From: kris@koehntopp.de (Kristian =?iso8859-1?q?K=F6hntopp?=) Newsgroups: de.comp.lang.php,de.answers,news.answers Subject: [FAQ] de.comp.lang.php Summary: These is the Frequently Asked Questions posting for the newsgroup de.comp.lang.php. Like the newsgroup, it is in German language. Expires: Mit, 3 Apr 2002 07:22:48 +0200 Content-Type: text/plain; charset=iso-8859-1 Content-Type-Encoding: 8bit Followup-To: de.comp.lang.php Archive-Name: de/comp/lang/php/faq Posting-frequency: monthly URL: http://www.koehntopp.de/php/faq.html 1. Über diese FAQ 1.1. [1]Was ist das hier? 1.2. [2]Wie ist die Charta dieser Newsgroup? 1.3. [3]Was ist PHP? 1.4. [4]Welche Version von PHP ist aktuell? 1.5. [5]Was bedeutet LAMP, WAMP und so weiter? 1.6. [6]Wo finde ich die aktuelle Version dieser FAQ? 1.7. [7]Kann ich eine Kopie der FAQ per Mail zugesendet bekommen? 1.8. [8]Du hast doch für die FAQ geschrieben. Ich habe da eine Frage zu PHP... 1.9. [9]Das ist eine tolle FAQ! Kann ich die als Unterrichtsmaterial verwenden? Kann ich sie drucken? 1.10. [10]Was soll ich tun, wenn ich einen Fehler in der FAQ gefunden habe? 1.11. [11]Kann ich selber für diese FAQ schreiben? 1.12. [12]Wo finde ich weitere Informationen über PHP? 1.13. [13]Welche Bücher gibt es über PHP? 1.14. [14]Soll ich Jobangebote in de.comp.lang.php posten? 1.15. [15]Wer kann mir einen Provider empfehlen? 1.16. [16]Warum bekomme ich Ermahnungsmails, wenn ich Autoren in der Gruppe auf Netiquetteverstöße aufmerksam mache? 1.17. [17]Warum bekomme ich Ermahnungsmails? 1.18. [18]Warum sind Flames sinnlos? 1.19. [19]Ich verwende Outlook Express und keiner hat mich lieb. 1.20. [20]Was ist TOFU? Wieso finden die Anderen meine Artikel schwer zu lesen? 1.21. [21]Wie verweise ich auf die FAQ? 1.22. [22]Wie stelle ich meine Frage an die Newsgroup am sinnvollsten? 1.1. Was ist das hier? | [23]dclp FAQ | [24]Einfuehrung | Antwort von [25]Kristian Köhntopp Dies ist die FAQ (FAQ = Frequently Asked Questions) für die Newsgruppe [26]de.comp.lang.php . Sie erklärt den Zweck der Newsgruppe, auf welche Weise man hier am einfachsten an sinnvolle Antworten kommt und dient als Sammlung von Antworten auf häufig gestellte Fragen in der Gruppe. Wenn Du Kommentare oder Vorschläge zu diesem Artikel hast oder wenn Du selber einige Abschnitte in diesen Artikel einbringen möchtest, wendest Du Dich am besten per Mail an die Mailingliste zur de.comp.lang.php FAQ, < [27]german-faq@lists.netuse.de >. 1.2. Wie ist die Charta dieser Newsgroup? | [28]dclp | Antwort von [29]Kristian Köhntopp Diese Newsgruppe richtet sich an alle Benutzer und Programmierer von PHP, einer Programmiersprache mit Schwerpunkt auf der Entwicklung von Webanwendungen. Es können alle Themen rund um PHP besprochen werden, seien es nun Probleme mit der Installation, der Anwendung oder Programmierung in PHP oder der Erweiterung des PHP- Interpreters selbst. 1.3. Was ist PHP? | [30]PHP Leistungsumfang | [31]PHP Homepage | Antwort von [32]Kristian Köhntopp Die Abkürzung PHP steht offiziell für "PHP: Hypertext Preprocessor". Dies ist eine rekursive Abkürzung im Stile des [33]GNU -Projektes. PHP ist eine Scriptsprache zur dynamischen Erstellung von Webseiten. Die Anweisungen der Sprache sind dabei in den HTML-Code einer Webseite eingebettet, d.h. jede HTML-Seite ist auch ein gültiges PHP-Programm. Die Syntax von PHP ist ähnlich wie die von C, Java oder JavaScript. Die Sprache zeichnet sich vor allen Dingen durch ihre leichte Erlernbarkeit, ihre ausgezeichneten Datenbankanbindungen und Internet-Protokolleinbindungen und die Unterstützung zahlreicher weiterer Funktionsbibliotheken aus. PHP stellt so für den Web-Entwickler das ideale Werkzeug zur Erstellung von dynamischen Inhalten dar. PHP ist freie Software im Sinne der [34]Debian Free Software Guidelines (DFSG) . Quelltext und Binaries des PHP-Interpreters sind frei erhältlich und können für alle kommerziellen und nichtkommerziellen Zwecke eingesetzt werden; jeder kann den PHP-Quelltext weiterentwickeln und die Änderungen an das PHP-Projekt zurückfließen lassen. Der genaue Lizenztext ist in der Datei license-php.txt enthalten, die Bestandteil der PHP-Distribution ist. PHP läuft auf allen gängigen Unix-Versionen und auf den verschiedenen Windows-Versionen (Windows 95/98/ME/NT/2000). Als CGI-Programm kann PHP mit jedem Webserver zusammenarbeiten. Für einige Webserver, allen voran [35]Apache , stehen auch Modulversionen zur Verfügung, die sehr viel effizienter ausgeführt werden. Die Homepage des PHP-Projektes ist [36]http://www.php.net . Mirrors dieser Site sind in vielen Ländern vorhanden, unter anderem auch in Deutschland unter der URL [37]http://www.php3.de oder [38]http://de.php.net . Von dort kann man die jeweils aktuelle Releaseversion des Interpreters sowie Binaries für eine Reihe von Plattformen herunterladen. Ebenso finden sich dort das Handbuch sowie Archive der englischen Mailinglisten. 1.4. Welche Version von PHP ist aktuell? Antwort von [39]Kristian Köhntopp Die aktuelle Produktionsversion von PHP ist Version 4.1.0. Nur diese Version sollte auf Produktionsmaschinen eingesetzt werden. Die letzte Version von PHP 3 ist Version 3.0.18. PHP 3 wird nicht mehr weiterentwickelt, es finden nur noch marginale Bugfixes statt. Die aktuellste Version ist immer auf der offiziellen [40]PHP Homepage verfügbar. 1.5. Was bedeutet LAMP, WAMP und so weiter? Antwort von [41]Kristian Köhntopp LAMP ist die Abkürzung für Linux , Apache , MySQL und PHP . Sie beschreibt ein System zur Entwicklung und zum Betrieb von Webanwendungen, bestehend aus Betriebssystem, Webserver, Datenbankserver und Programmiersprache. Analog steht die Abkürzung WAMP für Windows , die Windows-Version von Apache , die Windows-Version von MySQL und die Windows-Version von PHP . Viele PHP-Anwender entwicklen lokal auf WAMP und überspielen die fertigen Seiten dann auf einen LAMP- oder SAMP (Solaris, Apache, MySQL, PHP)-Server bei einem Provider. 1.6. Wo finde ich die aktuelle Version dieser FAQ? | [42]dclp FAQ | [43]Version | [44]Download | Antwort von [45]Kristian Köhntopp Eine Downloadversion dieser FAQ im HTML-Format findet sich unter der URL [46]http://www.koehntopp.de/php/faq-html.tar.gz . Windows-Anwender können die FAQ auch im CHM- oder HLP-Format laden, die in der jeweils aktuellsten Version unter der Adresse [47]http://www.koehntopp.de/php/faq.chm oder [48]http://www.koehntopp.de/php/faq.hlp abgelegt ist. (Zum Ansehen von CHM-Dateien ist eine hinreichend neue Version des Microsoft Internet Explorer notwendig). Die aktuelle Version dieser FAQ ist unter der URL [49]http://www.koehntopp.de/php zu lesen. Eine Version in einer einzigen Datei befindet sich unter der URL [50]http://www.koehntopp.de/php/faq-single.html . Die XML-Quelltexte dieser FAQ sind unter der URL [51]http://www.koehntopp.de/php/faq.tar.gz zu finden. 1.7. Kann ich eine Kopie der FAQ per Mail zugesendet bekommen? Antwort von [52]Kristian Köhntopp Die FAQ wird nicht als Mail versendet. Die Frage [53]Wo finde ich die aktuelle Version dieser FAQ? beschreibt, wie und in welchen Formaten die FAQ bezogen werden kann. 1.8. Du hast doch für die FAQ geschrieben. Ich habe da eine Frage zu PHP... | [54]Hilfe | Antwort von [55]Kristian Köhntopp Der Autor dieser Antwort erhält zur Zeit zwischen 40 und 60 private Fragen nach Hilfe zu PHP pro Woche. Keine dieser Fragen wird beantwortet - das ist arbeitsmäßig einfach nicht zu schaffen. Allgemein: Es ist sinnlos, Fragen per Mail an einen der Autoren dieser FAQ zu senden. Du belastest damit eine einzelne Person mit Arbeit, statt die Arbeit auf die Newsgroup zu verteilen. Außerdem ist diese Arbeit verschwendet, denn die Antwort wird nur von Dir und nicht von den anderen Lesern der Newsgroup gelesen. Auch kann die Antwort nicht vom FAQ-Team weiterverarbeitet werden. Sende Deine Frage bitte an die [56]Newsgroup . Keiner der Autoren der FAQ wird Dir privaten Support per Mail leisten. 1.9. Das ist eine tolle FAQ! Kann ich die als Unterrichtsmaterial verwenden? Kann ich sie drucken? | [57]Copyright | Antwort von [58]Kristian Köhntopp Dieser Text ist wie alle Werke urheberrechtlich geschützt. Er ist jedoch unter den Bedingungen der [59]Open Publication License , Version 0.4 oder höher verfügbar. Die genaue Lizenz findet sich in [60]Open Publication License . Wenn dieser Text reproduziert oder verwendet wird, bitten die Autoren um Meldung eines solchen Angebotes an [61]german-faq@lists.netuse.de unter Angabe einer Kontaktadresse. Diese Kontaktperson ist herzlich eingeladen, sich auf der Mailingliste [62]german-faq@lists.netuse.de anzumelden, um über Aktualisierungen des Textes informiert zu werden. 1.10. Was soll ich tun, wenn ich einen Fehler in der FAQ gefunden habe? | [63]Mailingliste | [64]german-faq | Antwort von [65]Martin Jansen Wenn Du einen Fehler in einem der Texte in der FAQ gefunden hast, dann bitten wir Dich, uns diesen mitzuteilen. Dazu schickst Du am besten eine E-Mail an [66]german-faq@lists.netuse.de . Unter dieser Adresse erreichst Du die Mailingliste der de.comp.lang.php-FAQ, welche alle Autoren der FAQ abonniert haben. 1.11. Kann ich selber für diese FAQ schreiben? | [67]Mailingliste | [68]german-faq | [69]CVS | Antwort von [70]Kristian Köhntopp Ja, sofern Du die in [71]Open Publication License beschriebene Lizenz für Dich akzeptieren kannst. Diese Lizenz bedeutet im wesentlichen, daß Du an Deinen eigenen Texten das volle Urheber- und Verwertungsrecht behältst, aber jedermann das Recht einräumst, die FAQ zu nutzen und unverändert und mit Hinweis auf die Originalquelle und die Originalautoren zu reproduzieren. Rein technisch benötigst Du die folgenden Utensilien: * Einen Rechner mit Texteditor und PHP zum Testen. * Einen [72]CVS-Client . * optional einen beliebigen XSLT-Prozessor (z.B. [73]Sablotron ). Es gibt eine Mailingliste [74]german-faq@lists.netuse.de , die Nachrichten über Änderungen an der FAQ enthält und bei der man Hilfe für Autoren bekommt. Man kann die Mailingliste unter der Adresse [75]german-faq-subscribe@lists.netuse.de bestellen. Es existiert ein CVS-Archiv, aus dem die aktuelle Version der FAQ bezogen werden kann. Die CVSROOT dieses Archives ist :pserver:cvsread@www.koehntopp.de:/repository mit dem Paßwort cvsread . Das Modul heißt german-faq . _________________________________________________________________ $ cvs -d :pserver:cvsread@www.koehntopp.de:/repository login Password: cvsread $ cvs -d :pserver:cvsread@www.koehntopp.de:/repository checkout german-faq ... # Aktualisieren der Version mit $ cd german-faq $ cvs -z9 update -dAP _________________________________________________________________ Diese Version ist immer aktueller als die auf dem Webserver veröffentlichte Version. 1.12. Wo finde ich weitere Informationen über PHP? | [76]Webressource | [77]Tutorial | [78]Anleitung | Antwort von [79]Kristian Köhntopp Zu PHP gibt es zahlreiche Informationsquellen in deutscher und englischer Sprache. Deutsche Ressourcen im WWW * [80]Artikel von Kristian Köhntopp * [81]Dynamic Webpages * [82]Jörg Baachs LAMP-Installationsanleitung * [83]Martin Jansens PHP Seiten * [84]PHP mit dem PWS (Windows Personal Web Server) * [85]PHP-Center * [86]PHP-Homepage * [87]Tutorials für PHP und MySQL * [88]WAMP HowTo * [89]PHP und MySQL Tutorial * [90]PHP4-Forum Internationale Ressourcen im WWW * [91]PHP Homepage * [92]Englische FAQ * [93]Zend ist die PHP4 Scripting Engine * [94]Annotated Online Manual * [95]WeberDev.com * [96]Devshed Developer Forum * [97]PHP Classes Repository * [98]PHP Manual for Homesite * [99]PHP3 Code Exchange * [100]Tutorials auf phWizard.net * [101]phpWizard.net * [102]phpbuilder.com * [103]PHP Functions Essential Reference Schulungsunterlagen für PHP * [104]ThinkPHP! , eine Website mit kostenlosen Schulungsunterlagen, Artikeln, weiterführendem KnowHow. * [105]PHP-Schulung von Ulf Wendel und Johann-Peter Hartmann * [106]PHP-Lehrgang bei Webmaster Resource Fertige Anwendungen in PHP Ein Verzeichnis von Projekten, die PHP verwenden, findet man im [107]Projektverzeichnis der offiziellen Homepage . * [108]Phorum , ein Diskussionsforum. * [109]phpSlash , ein Diskussionsforum. * [110]IMP , ein Webmail Interface. * [111]Bookmarker , eine Bookmark-Verwaltung. * [112]PHPLIB , eine objektorientierte Bibliothek zur Anwendungsentwicklung. * [113]phpMyAdmin , ein Managementsystem für MySQL-Datenbanken. * [114]MyGuestbook , ein Gästebuch. * [115]phpAds , ein Verwaltungssystem für Banner Ads. * [116]phpHoo , eine Art Mini-Yahoo. 1.13. Welche Bücher gibt es über PHP? | [117]Literatur | [118]Amazon | Antwort von [119]Martin Jansen In Deutsch: * [120]PHP 4. Dynamische Webauftritte professionell realisieren , Egon Schmid, Christian Cartus; unter Mitarbeit von Wolfgang Drews, Hartmut Holzgraefe, Kristian Köhntopp, Uwe Steinmann, Christian Wenz; Markt+Technik Verlag. * [121]PHP 4: Grundlagen und Profiwissen, Webserver-Programmierung unter Windows und Linux , Jörg Krause; Carl Hanser Verlag. * [122]PHP kurz & gut , Rasmus Lerdorf, dt. Übersetzung Ingo Marks; O'Reilly Verlag. * [123]PHP 4 Webserver-Programmierung für Um- und Einsteiger , Thomas Theis; Galileo Press. * [124]Das Einsteigerseminar PHP 4.0 , Dr. Susanne Wigard; BHV. * [125]PHP 4 + MySQL , Rolf D. Stoll, Gudrun Anna Leierer; Data Becker. * [126]PHP 4 - Tutorial & Referenz , Klaus Schmidt; Computer & Literatur Verlag GmbH. * [127]Jetzt lerne ich PHP 4 , Matt Zandstra; Markt+Technik Verlag. * [128]Webanwendungen mit PHP 4.0 entwickeln , Tobias Ratschiller, Till Gerken; Addison-Wesley. * [129]PHP 4 , Uwe Hess, Günther Karl; BHV. * [130]Oracle 8 PL/SQL Programmierung , Scott Urman; Hanser. In Englisch: * [131]PHP Pocket Reference , Rasmus Lerdorf; O'Reilly & Associates. * [132]Professional PHP Programming , Jesus Castagnetto, Harish Rawat, Sascha Schumann, Chris Scollo, Deepak Veliath; Wrox Press Inc. * [133]Core PHP Programming , Leon Atkinson; Prentice Hall. * [134]PHP3: Programming Browser-Based Applications , David Medinets; MacGraw-Hill. * [135]Building Database Applications on the Web Using PHP3 , Craig Hilton, Jeff Willis; Addison-Wesley. * [136]PHP Essentials A Better Way to Learn PHP - Includes Version 4, Julie C. Meloni; Prima-Tech. * [137]PHP Fast & Easy Web Development , Julie C. Meloni; Prima-Tech. * [138]PHP Functions Essential Reference , Zak Greant, Greame Merrall, Torben Wilson, Brett Michlitsch; New Riders * [139]SAMS Teach Yourself PHP4 in 24 Hours , Matt Zandstra; Sams. * [140]Web Application Development with PHP 4.0 , Tobias Ratschiller, Till Gerken; New Riders Publishing. * [141]PHP 4 Bible , Tim Converse, Joyce Park; IDG Books Worldwide. * [142]PHP and MySQL Web Development , Luke Welling, Laura Thomson; Sams. * [143]Beginning PHP 4 , Jon Blan, Wankyu Choi, Allan Kent, Ganesh Prasad, Chris Ullmann; Wrox Press Inc. * [144]PHP Developer's Cookbook , Sterling Hughes, with contributions from Andrei Zmievski; SAMS. * [145]PHP Developer's Dictionary , R. Allen Wyke, Michael J. Walker and Robert Cox; Sams. Weitere nützliche Titel: * [146]MySQL , Paul DuBois. * [147]Oracle 8i. Die umfassende Referenz. Version 8i, 8.0.x und 7.x. , George Koch, Kevin Loney; Carl Hanser Verlag. * [148]Oracle8 für den DBA. Verwalten, optimieren, vernetzen , Uwe Herrmann, Dierk Lenz, Günter Unbescheid; Addison-Wesley. * [149]Oracle 8. Tuning. Version 7 bis Version 8. , Michael J. Corey, Michael Abbey, Daniel J. Dechichio; Carl Hanser Verlag. * [150]Reguläre Ausdrücke. , Jeffrey E. F. Friedl. * [151]Programming Internet Email , Dave Wood; O'Reilly UK. Ein weiteres Bücherverzeichnis findet sich auf der offiziellen Homepage von PHP: [152]http://www.php.net/books.php 1.14. Soll ich Jobangebote in de.comp.lang.php posten? | [153]Spam | [154]dmabi | Antwort von [155]Kristian Köhntopp Eine kurze Umfrage im Januar 2000 in [156]de.comp.lang.php hat ergeben, daß Jobangebote in der Newsgroup toleriert werden, auch wenn sie nach Charta streng genommen off-topic sind - solange sie folgenden Ansprüchen an die äußere Form genügen: * Jobangebote sollen im Betrefftext des Artikels die Kennzeichnung [JOB] haben. Auf diese Weise sind sie leicht erkennbar und können von den Leuten, die sie nicht sehen wollen leicht unterdrückt werden, während Sie von den Leuten, die einen Job suchen, leicht gefunden werden. * Jobangebote sollten mit der Headerzeile Followup-To: poster veröffentlicht werden. de.comp.lang.php nimmt die Veröffentlichung von Jobangeboten hin, ist aber nicht zur Diskussion über Jobangebote gedacht. * Jobangebote sollten nicht übermäßig oft veröffentlicht werden: Jeder Job sollte nur genau einmal angepriesen werden und Arbeitgeber mit ständigen oder wiederkehrenden Jobangeboten sollten nicht öfter als einmal im Monat veröffentlichen. Es hat keinen Sinn sich aufzudrängen, und bei einem unbeliebten Arbeitgeber wird in der jetzigen Arbeitsmarktsituation wohl kaum jemand anfangen. * Jobangebote sollten Netiquettekonform sein: Der Absendername soll eine Person ("Paul Arbeitgeber") und keine Funktion ("Personalbüro Arbeitgeber GmbH") sein. Die angegebene Mailadresse soll gültig sein. In Newsartikel soll kein HTML verwendet werden und Netscape Visitenkarten sollen nicht verwendet werden - Hochglanz-Blendwerk ist nett für das Marketing, aber wir sind R&D. * Jobangebote können in die formal korrekte Newsgroup [157]de.markt.arbeit.biete.it-berufe crosspostet werden. Dann ist es doppelt wichtig, daß ein Followup-To: poster oder Followup-To: de.markt.arbeit.d gesetzt wird. de.comp.lang.php ist nicht zur Diskussion von Jobangeboten geeignet! Wenn Sie als Arbeitgeber nicht in der Lage sind, im USENET intelligent, kooperativ und regelkonform aufzutreten, sollten Sie andere Medien für Ihre Personalaquise verwenden, die sich Ihnen leichter erschließen. Ihre Corporate Identity wird es Ihnen danken. 1.15. Wer kann mir einen Provider empfehlen? | [158]dcpw | [159]Strato | [160]1&1 | [161]Puretec | [162]Webhoster | [163]Free Webspace | Antwort von [164]Kristian Köhntopp Eine kurze Umfrage im Februar 2000 in [165]de.comp.lang.php hat ergeben, daß Fragen nach Providern oder Providerspezifika in dieser Newsgroup nicht willkommen sind. Die korrekte Newsgroup für diese Frage ist [166]de.comm.provider.webspace . Ebenso unerwünscht sind providerspezifische Fragen wie Wie komme ich bei xyz an die MySQL-Datenbank? . Der korrekte Ansprechpartner für solche Fragen wäre der Support des betreffenden Providers bzw. dessen FAQ. Werden derartig providerspezifische Fragen dennoch in die Newsgroup gestellt, ist es höflich, Followup-To: poster zu setzen und hinterher eine Zusammenfassung der eingegangenen Mails zu posten. Eine Zusammenfassung besteht nicht darin, die Texte der eingegangenen Mails hintereinanderzuhängen und in die Gruppe zu werfen, sondern idealerweise in einem Text, der vergleichsweise schmerzlos in diese FAQ integriert werden kann. Eine Providerdatenbank wird unter anderem bei [167]php.net , [168]Dynamic Web Pages , beim [169]PHP-Center und bei [170]Webhostlist.de betrieben. 1.16. Warum bekomme ich Ermahnungsmails, wenn ich Autoren in der Gruppe auf Netiquetteverstöße aufmerksam mache? | [171]Netiquette | Antwort von [172]Kristian Köhntopp Du hast vollkommen Recht: Manche Autoren in der Newsgroup verstoßen gegen die Netiquette, wie sie in [173]de.newusers.infos gepostet wird. Sie tun dies etwa, indem sie ohne vollen Realnamen schreiben, inkorrekte Mailadressen ("nospam", "deletethis") angeben oder Artikel mit HTML oder Netscape-Visitenkarten versenden. Du sollst das auch nicht hinnehmen. In einer Newsgroup ist der Ton jedoch genauso wichtig wie der Inhalt. Die Regulars von [174]de.comp.lang.php sind stolz auf den freundlichen und hilfsbereiten Ton in ihrer Newsgroup. Wenn Du also einen anderen Autor an die Netiquette erinnern möchtest, dann tue dies bitte unbedingt per Mail und nicht öffentlich in der Gruppe. Auch die Netiquette, auf deren Einhaltung Du bestehst, fordert dies - Du kannst nicht auf der einen Seite auf der Einhaltung der Netiquette bestehen und andererseits selbst dagegen verstoßen, ohne Glaubwürdigkeit zu verlieren. Und bitte: Halte Deinen Ton auch in der Mail freundlich. Du wirst leichter verstanden und erreichst das gewünschte Ziel viel eher. Wenn Du meinst, Deinen Artikel dennoch öffentlich posten zu müssen, etwa um einen Autoren an die korrekte Newsgroup zu verweisen, oder weil die angegebene Mailadresse nicht erreichbar ist, oder weil sich der Autor per Mail nicht einsichtig zeigt und sich niemand sonst bisher darum gekümmert hat, dann halte Deinen Beitrag bitte freundlich und konstruktiv. Das bedeutet: Beantworte die gestellte Frage oder löse das Problem des Fragers so gut Du kannst und weise dann auf die Netiquette hin. Wenn Du zu dem Problem des Fragers nichts beizutragen hast, dann poste lieber gar nichts - oder schreibe eine Mail. Du bist nicht allein in der Gruppe und Du mußt die Welt nicht selbst retten. Ein anderer, der antworten kann, wird antworten und dabei wahrscheinlich auch auf korrektes Verhalten hinweisen. Regeldiskussionen gehören in die dafür vorgesehene Newsgroup, [175]de.soc.netzkultur.umgangsformen , oder sollen mit einem Followup-To: poster versehen werden. 1.17. Warum bekomme ich Ermahnungsmails? | [176]Netiquette | [177]TOFU | [178]Pseudo | Antwort von [179]Kristian Köhntopp Du wirst nicht nur in [180]de.comp.lang.php , sondern in den meisten anderen deutschen Newsgroups auf korrektes Verhalten in den Newsgroups hingewiesen, wenn ohne einen vollständigen Namen postest, Artikel ohne gültige Absenderadresse schreibst, Artikel mit Werbung absetzt, HTML oder Netscape-Visitenkarten in Deinen Artikeln versendest oder mutwillig Artikel in die falschen Newsgroups schreibst. Diejenigen von uns, die schon länger in den USENET News aktiv sind, haben sich diese Regeln und Verhaltenformen nicht aus Spaß ausgedacht. USENET existiert schon seit mehreren Jahrzehnten und die Verhaltensnormen, auf deren Einhaltung bestanden wird, haben sich in langen Jahren entwickelt und bewährt. Es gibt einen guten Einführungstext aus [181]de.newusers.infos mit dem Titel [182]Warum soll ich mich an die Regeln halten? der erklärt, warum die Dinge so sind, wie sie sind. Wenn Du von [183]de.comp.lang.php Ergebnisse möchtest, also technische Hilfe bei Deinen Problemen mit der Programmiersprache PHP, dann tust Du gut daran, Deinen Texten auch eine akzeptable äußere Form zu geben. 1.18. Warum sind Flames sinnlos? | [184]Netiquette | Antwort von [185]Kristian Köhntopp Newbies, die sich nicht an geltende Netzkulturen halten oder schlecht formulierte Fragen stellen, kommen meist mit einem konkreten Problem nach [186]de.comp.lang.php . Diese Leute bekommen dann allerdings häufig keine vernünftige Antwort, sondern werden mit Flames überhäuft. Der Grund liegt darin, daß beide Parteien mit unterschiedlichen Erwartungen und unterschiedlichen Kommunikationszielen in den Thread gegangen sind, und sie nicht bereit waren, von diesen Zielen abzuweichen. So ist keine sinnvolle Kommunikation zustande gekommen. Eine sinnvolle Antwort auf ein schlecht formuliertes oder unhöfliches Posting unterscheidet sich in den folgenden Punkten: Zunächst einmal versucht sie freundlich zu bleiben, ohne in der Sache nachzugeben. Dann geht sie unmittelbar auf das Problem des Posters ein, d.h. sie hilft ihm auf eine konstruktive Weise, sein unmittelbares Problem zu lösen, um ihn wieder arbeitsfähig zu machen. Dies ist der wichtigste Aspekt der Nachricht aus der Sicht des Newbies oder Posters: Es ist egal, wie unsystematisch und offtopic die Nachricht von ihm oder Deine Antwort ist - wenn Du mit ihm etwas anfangen willst, mußt Du zuerst seinen unmittelbaren Block lösen, damit Du sinnvolle Dinge nachschieben kannst. Nachschieben heißt in diesem Zusammenhang, den Newbie mit weiterführenden Informationen zu versorgen, damit er mehr lernt, als er mit seiner Frage eigentlich bezweckt hatte. "Nachschieben" ist wichtig, denn nur so bekommt man Newbies schrittweise zu Regulars umgebaut. Erst am Schluß eines Postings gibt es dann die Netiquette, quasi als Dressing obendrauf. Mit dem ganzen Zucker, der vorab geliefert worden ist, schmeckt das dann nicht mehr so bitter und dringt viel tiefer ein. Immerhin ist der Newbie ernst genommen worden und hat produktive Antworten bekommen, obwohl er sich mit seinem unerfahrenen Auftreten in [187]de.comp.lang.php ziemlich lächerlich gemacht hat - das ist wie in Shorts und T-Shirt auf eine Sitzung mit lauter Anzügen und Schlipsen zu kommen: "Selbstverständlich können wir Ihnen die 10.000 Tonnen Schweinehälften liefern, und übrigens Herr Graczoll, fällt Ihnen was an Ihrer Kleidung auf?" Als Abschluß nocheinmal die Arbeitsschritte für guten technischen Support in [188]de.comp.lang.php als Spickzettel: * Freundlich bleiben. Wenn Du nicht freundlich bleiben kannst, laß jemand anders die Arbeit machen. Wir sind genug Leute hier, Du mußt die Welt nicht alleine retten. Und wenn Du ausbrennst, ist uns damit auch nicht geholfen. * Den Block wegräumen. Der Neuling kommt mit einem unmittelbaren Problem in die Gruppe. Räume dieses Problem weg. Wenn Du dieses Problem nicht lösen kannst, laß den Neuling in Ruhe. Jemand anders wird sich darum kümmern, Du musst die Welt nicht alleine retten. Bevor der Neuling nicht aufgemacht ist, kann man sekundäre Probleme nicht lösen. Auf den Neuling einzuschlagen, bevor er aufgemacht ist, ist kontraproduktiv und macht die Arbeit für andere nur schwieriger. Mache Deinen Kollegen die Arbeit nicht schwierig - wenn Du nicht aufmachen kannst, laß den Fall liegen. * Nachschieben. Ein Neuling ohne Block ist eine Gelegenheit. Nutze sie! Rette die Welt. Drück ihm nach der unmittelbaren Antwort auf sein konkretes Problem noch eine Winzigkeit mehr rein, damit der arme Kerl das Licht sehen kann. Wenn Du ihm in 2. eine Query gebaut hast, zeig ihm Zusatzinfo zu SQL. Wenn er ein Problem mit den MySQL-Funktionen hatte, zeig ihm die passenden (nicht irgendwelche, die passenden!) Handbuchseiten. Wenn er ein Sicherheitsloch gebaut hatte, zeig ihm passende Zusatzinfo. Präsentiere diese Zusatzinfo so, daß dem Neuling der Mehrwert Deiner Antwort deutlich wird, und daß er motiviert ist, sich diese Information zu erarbeiten. * Geradebiegen. Wenn die Frage des Neulings Formfehler hatte, weise sachlich (!) und beiläufig auf diese Formfehler hin. Niemand will auf einer Party mit dem Megaphon ausgerufen werden: "Herr XYZ wird gebeten, den Hosenstall zu schließen." Andererseits will auch niemand den Nudelsketch von Loriot nachdrehen. Ergänze Deine Antwort wieder mit passenden URLs, etwa dem Abschnitt der FAQ, oder direkt mit den Links, die in der FAQ enthalten sind. * Wir helfen Dir. Du mußt die Welt nicht alleine retten. Wir haben die FAQ speziell für Dich gebaut - mit der FAQ ist es einfacher und schneller für Dich, produktiv zu helfen, statt eine Flame zu schreiben: 30 Sekunden für eine entspannte Nachricht mit zwei FAQ-Zitaten statt mindestens vier Minuten Streß, um den Deppen manuell fertig zu machen. 1.19. Ich verwende Outlook Express und keiner hat mich lieb. | [189]TOFU | [190]Microsoft | [191]Netiquette | Antwort von [192]Kristian Köhntopp Das wird daran liegen, daß Du Dein Outlook Express nicht korrekt konfiguriert hast. Wahrscheinlich setzt Outlook Express nicht den korrekten Absendernamen, veröffentlicht Artikel in HTML oder in HTML- und Text-Versionen in doppelter Ausführung oder macht andere Dinge, die außer Microsoft niemand gut findet. Bitte lies die [193]Outlook Express FAQ , die für Deine Version von Outlook zutreffend ist und konfiguriere Deinen Newsreader korrekt. Auf [194]http://www.mayn.de/support/os/win95/outlook.htm gibt es eine bebilderte Anleitung, wie man mit Outlook richtige Quotezeichen einstellt und das proprietäre "AW:" in Antworten auf das richtige "Re:" umstellt. 1.20. Was ist TOFU? Wieso finden die Anderen meine Artikel schwer zu lesen? | [195]Netiquette | [196]TOFU | Antwort von [197]Kristian Köhntopp Text Oben, Fullquote Unten. Eine Unart, die einen nicht nur in dieser Newsgroup, sondern im ganzen Netz unbeliebt macht. Lies [198]http://learn.to/quote/ von Dirk Nimmich, und speziell Abschnitt 2.3 "Warum soll ich meine Antwort nach dem Zitat plazieren?" und die folgenden. 1.21. Wie verweise ich auf die FAQ? Antwort von [199]Kristian Köhntopp Ein Hinweis auf die FAQ sollte niemals einfach nur in der Form "RTFM" oder [200]http://www.koehntopp.de/php erfolgen, sondern immer auf eine konkrete Antwort zeigen. Der Leser sollte außerdem erfahren, welche Antwort er dort findet und wieso das Bezug zu der gestellten Frage hat. In der Newsgroup hat sich folgendes Format eingebürgert: _________________________________________________________________ 1.21 Wie verweise ich auf die FAQ? http://www.koehntopp.de/php/faq-about.html#about-21 _________________________________________________________________ 1.22. Wie stelle ich meine Frage an die Newsgroup am sinnvollsten? Antwort von [201]Martin Jansen Um in [202]de.comp.lang.php eine sinnvolle Antwort zu erhalten, die Dir weiterhilft, solltest Du versuchen, Dich an die folgenden Regeln zu halten: * Lies' möglichst zuerst in der Gruppe mit, um welche Themen es gerade geht. Nichts ist ätzender, als drei Mal am Tag dieselben Fragen lesen zu müssen. Recherchiere in den Newsgroup-Archiven bei [203]www.emre.de oder [204]groups.google.com nach deinem Problem - die meisten Fragen wurden schon gestellt und beantwortet. Zu fast allen PHP-Funktionen finden sich in den User Contributed Notes wertvolle Hinweise zur Verwendung, zu möglichen Stolperfallen, Plattformunterschieden etc.: [205]www.php.net/[Funktionsname] . * Konfiguriere Deinen Newsreader so, dass er in Deinen Postings Deinen richtigen Namen und Deine gültige E-Mail-Adresse anzeigt. * Stelle Deine Frage höflich und werden nicht ausfallend, auch wenn Dir nicht sofort jemand antwortet: Der Support, den Du im Usenet erhältst, ist im Gegensatz zu Hotlines etc. kostenlos und wird von anderen Leuten in Ihrer (teils knappen) Freizeit ohne eine Gegenleistung durchgeführt. * Damit Andere Dein Problem so gut wie möglich verstehen, solltest Du bei der Beschreibung Deines Problems so präzise wie möglich sein: Eine Beschreibung im Stil von "Mein Skript funktioniert nicht! Woran kann das liegen??" ist nicht sehr hilfreich, da Du sehr wenige Angaben machst. Versuche bei der exakten Beschreibung Deines Problems möglichst genaue Angaben über die verwendete PHP-Version, das verwendete Betriebssystem und über Software zu machen, die zusammen mit PHP eingesetzt wird (z.B. Webserver, Datenbanksystem). * Wenn Du Deinen Code in ein Posting einbaust, übernehme bitte nur die relevanten Zeilen, in denen Du den Fehler vermutest. Unnötige Skriptzeilen erhöhen die Größe Deines Postings und verärgern andere User, die diese unnötige Bandbreite bei Ihren manchmal sehr langsamen Verbindungen zum Usenet sehr eindrucksvoll zu spüren bekommen. Darüber hinaus haben viele Leute keine Lust, wegen eines kleinen Problems gleich ein Skript von mehreren Hundert Zeilen für Dich zu "debuggen". * Bitte übernehme Deinen Code, wenn Du ihn in Dein Posting integrierst, so, wie er auch im PHP-Skript steht: Alle Betriebssysteme unterstützen die Funktion "Copy & Paste". Nutze sie und komme bitte nicht auf die Idee, Deinen Code vom Skript in das Posting abzutippen! Durch das manuelle Übertragen erhöht sich die Chance, dass Du weitere Fehler in Deine ohnehin schon fehlerhaftes Skript einbaust. Damit machst Du es anderen Benutzer der Newsgroup nicht gerade leicht, die "wirklichen Fehler" zu finden. 2. Installation und Inbetriebnahme 2.1. [206]Suse Linux: Wie installiere ich PHP? 2.2. [207]Suse Linux 6.2 und 6.3: Warum funktionieren die libgd-Funktionen nicht korrekt? 2.3. [208]Wie compiliere ich ein aktuelles PHP auf Linux mit Apache Server? 2.4. [209]Ich habe Probleme PHP selbst zu compilieren. 2.5. [210]Wie installiere ich PHP auf Unix mit Netscape Server? 2.6. [211]Wie installiere ich CGI-PHP auf einem Apache-Server? 2.7. [212]Wie installiere ich PHP auf Windows? 2.8. [213]Was ist PHP/FI und wo kann ich es bekommen? Was ist phtml? 2.9. [214]Linux: Meine shared libraries werden nicht gefunden. 2.10. [215]Wie übersetze ich PHP4? 2.11. [216]SuSE Linux: Beim compilieren wird lex nicht gefunden 2.1. Suse Linux: Wie installiere ich PHP? | [217]Suse | [218]Installation | [219]Linux | Antwort von [220]Kristian Köhntopp Suse Linux enthält bereits ab Werk alle notwendigen Komponenten, um PHP-Anwendungen entwickeln zu können. Es empfiehlt sich jedoch der Einsatz einer aktuellen Version von Suse Linux (derzeit 7.2), damit die eingesetzten Komponenten aktuell sind. 2.2. Suse Linux 6.2 und 6.3: Warum funktionieren die libgd-Funktionen nicht korrekt? | [221]Suse | [222]libgd | [223]Linux | Antwort von [224]Kristian Köhntopp In Suse Linux 6.2 war der libgd-Support fehlerhaft gebaut. Beim Aufruf von [225]imagegif() stürzte der gesamte Apache-Subprozeß ab. In Suse Linux 6.3 war PHP 3.0.12 und libgd 1.6 mit FreeType-Support enthalten. Suse Linux selbst enthält libgd 1.7.x. Dies ist inkonsistent. Wenn PHP mit Support fuer libgd übersetzt wird, dann können unabhängig voneinander die Funktionen [226]imagecreatefromgif() und [227]imagegif() bzw. [228]imagecreatefrompng() und [229]imagepng() enthalten sein. In Suse Linux 6.3 waren jedoch weder die einen noch die anderen Funktionen enthalten. Es war daher notwendig, sich einen eigenen PHP-Interpreter mit den aktuellen Funktionen selbst zu compilieren. Alternativ kann man sich vom [230]Suse Supportserver eine aktualisierte Version von mod_php.rpm holen (Für Suse Linux 6.3 eine Version 3.0.14 mit funktionierendem libgd truetype-Support). In Suse Linux 6.4 und 7.0 funktioniert alles korrekt. 2.3. Wie compiliere ich ein aktuelles PHP auf Linux mit Apache Server? | [231]Apache | [232]kompilieren | [233]Linux | [234]Server | [235]Installation | Antwort von [236]Kristian Köhntopp Eine gute Hilfe ist das [237]Apache Compile Kit . 2.4. Ich habe Probleme PHP selbst zu compilieren. | [238]Problem | [239]kompilieren | [240]Bibliothek | [241]Linux | Antwort von [242]Kristian Köhntopp config.cache nicht gelöscht. Beim Selbstübersetzen von PHP werden gelegentlich neu installierte Bibliotheken nicht oder in der falschen Version gefunden. In diesem Fall ist meistens eine veraltete Datei config.cache aus einem früheren Lauf von configure schuld. Diese Datei muß gelöscht werden, danach werden die neuen Bibliotheken korrekt gefunden. Antwort von Markus Dobel | [243]Redhat | PHP 3.0.13 als Modul auf RedHat 6.x. In RedHat 6.x ist /usr/local/lib nicht im Standardsuchpfad für shared libraries enthalten. Man muß also nach der Installation von Zusatzbibliotheken wie pdflib und anderen die Datei /etc/ld.so.conf um dieses Verzeichnis erweitern und dann den Cache des Loaders aktualisieren, indem man ldconfig -v aufruft. Außerdem sollte man dringend die Ratschläge in der Datei INSTALL.REDHAT lesen, die den PHP-Quellen beiliegt. Bei einigen Versionen von RedHat ist das Konfigurationsprogramm apxs defekt: Es gibt falsche Pfade aus. Wie in der Datei beschrieben muß dieser Fehler erst korrigiert werden, bevor man mit der eigentlichen Installation fortfahren kann. 2.5. Wie installiere ich PHP auf Unix mit Netscape Server? | [244]Installation | [245]Netscape | [246]Solaris | [247]Server | Antwort von [248]Kristian Köhntopp Installation im einzelnen (Solaris 2.6): Sei der Name der Zielmaschine ghost. Sei die Suitespot- Installation in /opt/local/suitespot-3.0/ mit dem Webserver in /opt/local/suitespot-3.0/https-ghost . Sie die DocumentRoot /opt/local/www/pages , das CGI-Verzeichnis in /opt/local/www/cgi-bin . * Der Server laufe so, daß er statische Seiten aus der DocRoot und CGI-Programme aus dem /cgi-bin/ starten kann. * Man übersetze das nachstehende [249]Modul , das dem Netscape Server beibringt, wie man ein Redirect auf ein CGI-Programm aufgrund einer Dateiendung vornimmt (die Dateien sind auch auf dem [250]CVSweb von NetUSE zu bekommen). * Man kopiere die resultierende redirect_cgi.so -Datei nach /opt/local/suitespot-3.0/plugins/redirect/ . Dieses Verzeichnis ist zuvor anzulegen. * Man erzeuge einen passenden MIME-Type in /opt/local/suitespot-3.0/https-ghost/config/mime.types : _____________________________________________________________ type=magnus-internal/php exts=php3,phtml _____________________________________________________________ Einzutragen ganz am Ende im selben Block wie die anderen magnus-internal Typen. * Man trage das Plugin in der obj.conf ein, ganz am Anfang, nach den anderen Init-Statements: _____________________________________________________________ Init fn="load-modules" \ funcs="redirect-cgi" \ shlib="/opt/local/suitespot-3.0/plugins/redirect/redirect_cgi.so" \ NativeThread="no" _____________________________________________________________ * Man trage einen ObjectType in der obj.conf für das Default-Objekt ein, nach dem type-by-extension , vor dem force-type ObjectType: _____________________________________________________________ ObjectType fn="type-by-extension" ObjectType fn="redirect-cgi" \ cgi_path="/opt/local/www/cgi-bin/php" \ type="magnus-internal/php" \ debug="no" ObjectType fn="force-type" type="text/plain" _____________________________________________________________ * Wichtig! Man erzeuge eine Service-Method für den MIME-Type magnus-internal/cgi für das Default-Object. Wenn man das nicht macht, gibt es keinen CGI-Handler und damit kann der Request nicht geserviced werden. Also nach dem letzten ObjectType, vor dem ersten Service kommt: _____________________________________________________________ Service method="(GET|HEAD|POST)" \ type="magnus-internal/cgi" \ fn="send-cgi" _____________________________________________________________ Die Reihenfolge aller dieser Einträge ist kritisch. 2.6. Wie installiere ich CGI-PHP auf einem Apache-Server? | [251]Installation | [252]Apache | [253]Server | [254]CGI | Antwort von [255]Kristian Köhntopp Schritt 1: Man definiert ein Verzeichnis als CGI Verzeichnis: _________________________________________________________________ Options ExecCGI AllowOverride None _________________________________________________________________ Hier wird das Verzeichnis /home/www/.../cgi als Verzeichnis zur Ausführung von CGI-Dateien gekennzeichnet, indem für das Verzeichnis das ExecCGI Attribut gesetzt wird. Schritt 2: Man definiert für dieses Verzeichnis eine URL _________________________________________________________________ ScriptAlias /cgi/ /home/www/servers/phplib.shonline.de/cgi/ _________________________________________________________________ Die URL /cgi/ wird jetzt auf dieses Verzeichnis /home/www/.../cgi/ abgebildet. Man kann jetzt in diesem Verzeichnis eine ausführbare Datei (etwa ein Shellscript) ablegen, die den folgenden Text ausgibt, wenn man sie startet. _________________________________________________________________ Content-Type: text/plain Hallo, Welt. _________________________________________________________________ Wenn man diese Datei unter der URL http://meinserver.de/cgi/name_des-scripts aufruft, muss man den Text "Hallo, Welt." angezeigt bekommen. Gelingt dies nicht, sollte man durch das Error Log toben und nachsehen, was dort schiefgeht. Schritt 3: Man installiert den PHP-Interpreter im CGI-Verzeichnis und prüft, ob man ihn als CGI-Programm aufrufen kann. Dazu ist der Interpreter nach /home/www/.../cgi zu kopieren und dann unter der URL http://.../cgi/php oder http://.../cgi/php.exe oder wie immer der Interpreter heißt aufzurufen. Dabei muß die folgende Fehlermeldung kommen: _________________________________________________________________ Security Alert! PHP CGI cannot be accessed directly. This PHP CGI binary was compiled with force-cgi-redirect enabled. This means that a page will only be served up if the REDIRECT_STATUS CGI variable is set. This variable is set, for example, by Apac he's Action directive redirect. _________________________________________________________________ Kommt die Meldung nicht, ist das PHP-Binary unsicher und sollte durch ein korrekt compiliertes Binary ersetzt werden. Schritt 4: Man definiert eine Scriptaktion, die das o.a. Binary startet und definiert eine Endung, die durch diese Aktion abgehandelt wird: * _____________________________________________________________ Action php3-script /cgi/php _____________________________________________________________ teilt dem Apache mit, daß der interne MIME-Typ php3-script durch die URL /cgi/php (oder wie immer die URL zum Aufruf des PHP-Interpreters aus Schritt 3 heißt) abgehandelt wird. Der MIME-Typ php3-script ist illegal und das ist gut so, denn er wird nur innerhalb von Apache verwendet (er darf nur innerhalb von Apache verwendet werden!). * _____________________________________________________________ AddHandler php3-script .php3 _____________________________________________________________ teilt Apache weiterhin mit, daß die Endung .php3 durch den MIME-Handler für den MIME-Type php3-script abgehandelt wird. 2.7. Wie installiere ich PHP auf Windows? | [256]mod_php | [257]Windows | [258]Win32 | [259]Installation | [260]Bibliotheken | [261]Modul | [262]Apache | [263]Download | Antwort von [264]Martin Jansen Bei der Installation von PHP unter Windows hat man ebenso wie unter Unix/Linux die Möglichkeit, PHP als CGI-Version oder als Modul für den Apache Webserver zu installieren. Die Vor- und Nachteile der verschiedenen Versionen werden im Artikel [265]CGI PHP oder Modul? erläutert. Sowohl die CGI- als auch die Modul-Version sind auf der offiziellen [266]Downloadseite verfügbar. Mit der Modul-Version ist es zur Zeit jedoch nicht möglich, die GD-Library bzw. die Sablotron-Library zu nutzen, da diese nicht threadsafe sind. Daniel Beulshausen, der Initiator der Publizierung von PHP als Apache Modul für Windows, und Andreas Otto bieten auf der Seite [267]http://www.php4win.de neben der jeweils aktuellsten offiziellen Version auch Development-Versionen für experimentierfreudige Anwender an. Die offizielle Installationsanleitung für die CGI-Version in englischer Sprache findet sich auf dem [268]offiziellen PHP-Server . In deutscher Sprache findet man zur selben Frage eine umfangreiche [269]WAMP-Anleitung von Thomas Schulz (inzwischen auf Dynamic Webpages umgezogen). Zur Installation der Modul-Version schrieb [270]Björn Höhrmann am 20.07.2000 in de.comp.lang.php folgendes: _________________________________________________________________ Zur Installation: WinNT4: E:\Winnt\ Apache: E:\winapp\apache\ PHP4 Module: E:\winapp\apache\php4module\ E:\winapp\apache\conf\httpd.conf: + LoadModule php4_module php4module/php4apache.dll + AddType application/x-httpd-php .php E:\winapp\apache\php4module\php.ini: > Kopiert nach E:\Winnt\ [...] E:\winapp\apache>set path=%path%;e:\winapp\apache\php4module\ E:\winapp\apache>apache -k start Apache/1.3.11 (WunLix) PHP/4.0.2-dev running... [...] _________________________________________________________________ Darüber hinaus gibt es [271]PHP Triad , das eine komplette Serverumgebung inkl. PHP und MySQL auf einem Windows-Rechner installiert. 2.8. Was ist PHP/FI und wo kann ich es bekommen? Was ist phtml? | [272]PHP/FI | [273]phtml | [274]PHP2 | Antwort von [275]Kristian Köhntopp PHP/FI oder auch PHP2 ist der Vorläufer von PHP3. PHP/FI-Dateien haben oft die Endung .phtml . Es wird nicht mehr gewartet oder weiterentwickelt. Von einer Verwendung von PHP/FI in laufenden Projekten ist dringend abzuraten, auch dann, wenn noch alter PHP/FI-Code vorliegt. * PHP/FI hat keinen richtigen Parser. Es gibt Situationen, in denen syntaktisch korrekte Anweisungen vom PHP/FI-Interpreter nicht korrekt verarbeitet werden. * PHP/FI ist nicht Jahr-2000 sicher. Mindestens die Erzeugung von Cookies funktioniert bei PHP/FI nicht korrekt. * PHP/FI wird nicht mehr supported. Der Code wird nicht weiter gepflegt. * Es gibt Konverter, die PHP/FI in PHP3 übersetzen. Ein solches Programm, convertor , ist Bestandteil der PHP3-Distribution und kann verwendet werden, um PHP/FI-Code in PHP3 zu übersetzen. Der Code genügt dann zwar noch immer nicht den Anforderungen von modernen Codierrichtlinien, aber er ist immerhin durch den PHP3-Interpreter ausführbar. 2.9. Linux: Meine shared libraries werden nicht gefunden. | [276]Linux | [277]shared | [278]library | [279]nicht gefunden | [280]Apache | Antwort von [281]Kristian Köhntopp Übersetzt man PHP selbst und bindet dabei auch selbst installierte Module und Bibliotheken mit ein, kann es vorkommen, daß das Modul vom Apache nicht geladen wird, weil diese externen Bibliotheken nicht gefunden werden. Im folgenden wird erläutert, was es mit diesen Bibliotheken auf sich hat. _________________________________________________________________ kris@valiant:~ > ldd /bin/ls libc.so.6 => /lib/libc.so.6 (0x4001f000) /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000) _________________________________________________________________ Damit man das Kommando ls ausführen kann, müssen die Dateien ld-linux.so.2 und libc.so.6 vorhanden sein. Diese Dateien sucht mein System in den in /etc/ld.so.conf genannten Verzeichnissen. Damit dies schneller geht, legt das Kommando ldconfig einen Cache der Bibliotheken in diesen Verzeichnissen an. Mit diesem Caches wird ein Durchsuchen aller Verzeichnisse in /etc/ld.so.conf beim Start von Programmen vermieden. Stattdessen stehen in /etc/ld.so.cache Paare von Bibliotheksname und Pfadname, die sofort zugreifbar sind. Ein laufendes ls hat also /lib/ld-linux.so.2 und /lib/libc.so.6 geladen. Das kann man auch nachsehen: _________________________________________________________________ kris@valiant:~ > sleep 3600 & [1] 27091 kris@valiant:~ > cat /proc/27091/maps 08048000-0804a000 r-xp 00000000 08:15 63535 /bin/sleep 0804a000-0804b000 rw-p 00001000 08:15 63535 /bin/sleep 40000000-40013000 r-xp 00000000 08:15 71682 /lib/ld-2.1.3.so 40013000-40014000 rw-p 00012000 08:15 71682 /lib/ld-2.1.3.so 4001e000-4001f000 rw-p 00000000 00:00 0 4001f000-400f9000 r-xp 00000000 08:15 71690 /lib/libc.so.6 400f9000-400fe000 rw-p 000d9000 08:15 71690 /lib/libc.so.6 400fe000-40101000 rw-p 00000000 00:00 0 bfffe000-c0000000 rwxp fffff000 00:00 0 kris@valiant:~ > ldd `which sleep` libc.so.6 => /lib/libc.so.6 (0x4001f000) /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000) kris@valiant:~ > ls -l /lib/ld-linux.so.2 lrwxrwxrwx 1 root root 11 Apr 11 18:16 /lib/ld-linux.so.2 -> ld-2.1.3.so kris@valiant:~ > kill %1 kris@valiant:~ > [1]+ Terminated sleep 3600 _________________________________________________________________ Tatsächlich gibt die Ausgabe der Memory-Map für den Prozess 27091 (sleep) an, dass /lib/ld-2.1.3.so aka /lib/ld-linux.so.2 und /lib/libc.so.6 geladen sind - man kann deutlich die schreibgeschützten Code-Segmente ( r-xp ) und die überschreibbaren, nicht ausführbaren Datensegmente ( rw-p ) dieser Bibliotheken erkennen. Will ich nun mein PHP4 starten, dann wird _________________________________________________________________ kris@valiant:~ > ldd /usr/lib/apache/libphp4.so libgdbm.so.2 => /usr/lib/libgdbm.so.2 (0x4015b000) libpam.so.0 => /lib/libpam.so.0 (0x40162000) librecode.so.0 => /usr/lib/librecode.so.0 (0x4016a000) libdl.so.2 => /lib/libdl.so.2 (0x401e6000) libsnmp.so => /usr/lib/libsnmp.so (0x401ea000) libpdf.so.0 => /usr/lib/libpdf.so.0 (0x40225000) libtiff.so.3 => /usr/lib/libtiff.so.3 (0x4024e000) libldap.so.1 => /usr/lib/libldap.so.1 (0x40291000) libttf.so.2 => /usr/lib/libttf.so.2 (0x402a6000) libjpeg.so.62 => /usr/lib/libjpeg.so.62 (0x402cf000) libpng.so.2 => /usr/lib/libpng.so.2 (0x402ee000) libz.so.1 => /usr/lib/libz.so.1 (0x4030b000) libresolv.so.2 => /lib/libresolv.so.2 (0x4031b000) libm.so.6 => /lib/libm.so.6 (0x4032a000) libcrypt.so.1 => /lib/libcrypt.so.1 (0x40347000) libnsl.so.1 => /lib/libnsl.so.1 (0x40374000) liblber.so.1 => /usr/lib/liblber.so.1 (0x4038b000) libc.so.6 => /lib/libc.so.6 (0x40390000) /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x80000000) _________________________________________________________________ geladen, d.h. ein _________________________________________________________________ kris@valiant:~ > grep LoadModule /etc/httpd/httpd.conf | grep php LoadModule php4_module /usr/lib/apache/libphp4.so _________________________________________________________________ in der Apache-Konfiguration kann nur dann gelingen, wenn all die o.a. Bibliotheken vorhanden sind. Sind sie es nicht, schlägt das Laden des Moduls fehl: _________________________________________________________________ kris@valiant:~ > su - Password: valiant:~ # mv /usr/lib/libpng.so.2 /usr/lib/libpng.so.2.offline valiant:~ # ldd /usr/lib/apache/libphp4.so libgdbm.so.2 => /usr/lib/libgdbm.so.2 (0x4015b000) libpam.so.0 => /lib/libpam.so.0 (0x40162000) librecode.so.0 => /usr/lib/librecode.so.0 (0x4016a000) libdl.so.2 => /lib/libdl.so.2 (0x401e6000) libsnmp.so => /usr/lib/libsnmp.so (0x401ea000) libpdf.so.0 => /usr/lib/libpdf.so.0 (0x40225000) libtiff.so.3 => /usr/lib/libtiff.so.3 (0x4024e000) libldap.so.1 => /usr/lib/libldap.so.1 (0x40291000) libttf.so.2 => /usr/lib/libttf.so.2 (0x402a6000) libjpeg.so.62 => /usr/lib/libjpeg.so.62 (0x402cf000) <<< libpng.so.2 => not found >>> libz.so.1 => /usr/lib/libz.so.1 (0x402ee000) libresolv.so.2 => /lib/libresolv.so.2 (0x402fe000) libm.so.6 => /lib/libm.so.6 (0x4030d000) libcrypt.so.1 => /lib/libcrypt.so.1 (0x4032a000) libnsl.so.1 => /lib/libnsl.so.1 (0x40357000) liblber.so.1 => /usr/lib/liblber.so.1 (0x4036e000) libc.so.6 => /lib/libc.so.6 (0x40373000) /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x80000000) valiant:~ # mv /usr/lib/libpng.so.2.offline /usr/lib/libpng.so.2 valiant:~ # logout kris@valiant:~ > _________________________________________________________________ Note: Do not try this at home, kids! Wenn man das mit der falschen Bibliothek macht (ld-linux und libc sind gute Kandidaten), kann man schon mal die Rettungs-CD rauskramen und die Termine am Nachmittag absagen. Wenn die fehlende Bibliothek also nicht installiert ist, kann es nicht gehen. Wenn die fehlende Bibliothek nicht gefunden wird, dann ist /etc/ld.so.conf unvollständig und muß ergänzt werden. Wenn die fehlende Bibliothek nicht gefunden wird, aber installiert ist und in einem in /etc/ld.so.conf genannten Pfad installiert ist, dann muss ldconfig neu aufgerufen werden, damit /etc/ld.so.cache neu gebaut wird. 2.10. Wie übersetze ich PHP4? | [282]Installation | [283]kompilieren | [284]uebersetzen | Antwort von [285]Kristian Köhntopp Um PHP4 als Modul für den mit Suse Linux 6.4 gelieferten Webserver zu übersetzen, verwende ich das folgende Script do-conf : _________________________________________________________________ cp $0 $0.suse # cvs update -d -P rm -f .deps ./buildconf ./configure \ --with-java=/usr/lib/jdk1.1.7 \ --with-pgsql=/usr/lib/pgsql \ --with-mysql=/usr \ --with-ldap=yes \ --with-gd=yes \ --with-zlib=yes \ --with-xml \ --with-ttf \ --with-yp \ --with-ftp \ --with-snmp \ --with-config-file-path=/etc/httpd \ --with-apxs=/usr/sbin/apxs \ --with-exec-dir=/usr/lib/apache/bin \ --enable-versioning \ --enable-track-vars \ --enable-magic-quotes \ --enable-safe-mode \ --enable-sysvsem \ --enable-sysvshm \ --enable-thread-safety \ i386-suse-linux-gnu make depend make -j 2 _________________________________________________________________ 2.11. SuSE Linux: Beim compilieren wird lex nicht gefunden | [286]Suse | [287]Installation | [288]kompilieren | [289]lex | [290]Fehler | [291]uebersetzen | [292]nicht gefunden | Antwort von [293]Matthias P. Wuerfl Um PHP auf den aktuellen SuSE-Distributionen zu installieren braucht man flex. Wenn flex nicht installiert ist, dann bricht ./configure mit Meldungen wie _________________________________________________________________ checking for flex... no checking for lex... no ./configure: flex: command not found checking for flex... lex checking for yywrap in -ll... no checking lex output file root... ./configure: lex: command not found configure: error: cannot find output from lex; giving up _________________________________________________________________ ab. Flex findet man in der Serie d auf den SuSE-CDs und auch auf dem SuSE ftp-Server. Es kann einfach per YaST installiert werden. 3. Allgemeine Fragen zu PHP 3.1. [294]Wie vergleicht sich PHP mit anderen bekannten Webentwicklungssystemen? 3.2. [295]Wie vergleicht sich die Performance von PHP zu Perl? 3.3. [296]Wie kann ich mein ASP-Programm in PHP übersetzen? 3.4. [297]CGI PHP oder Modul? 3.5. [298]PHP-Scripte von Windows nach Unix portieren? 3.6. [299]Welche Editoren sind für PHP geeignet? 3.7. [300]Zeitgesteuerte PHP-Scripte und Shellscripte 3.8. [301]Wie bette ich PHP in HTML ein? (Beispielprogramm) 3.9. [302]Wie finde ich heraus, wie mein PHP-Interpreter konfiguriert ist? 3.10. [303]Wo finde ich die php.ini? 3.11. [304]Wie kann ich auf Umgebungsvariablen zugreifen? 3.12. [305]Wie kann ich auf den HTTP-Request-Header zugreifen? 3.13. [306]Gibt es noch mehr interessante Variablen im Environment? 3.14. [307]Ich verwende PHP (Version 3) als Apache-Modul. Wie kann ich dies konfigurieren? 3.15. [308]Was bedeuten master value und local value in phpinfo()? 3.16. [309]Welche Konfigurationsvariablen kann ich nicht in .htaccess-Dateien verwenden? 3.17. [310]Was genau bewirkt safe_mode und ist das sicher? 3.18. [311]Was ist --enable-force-cgi-redirect? Warum enthält $PHP_SELF den Pfad zum CGI-Interpreter? 3.19. [312]Warum funktioniert set_time_limit() nicht wie angepriesen? 3.20. [313]Wie kann ich auf Kommandozeilen-Argumente zugreifen? 3.21. [314]Wie kann ich einen Parameter von einer PHP-Seite an eine andere weitergeben? 3.22. [315]Wie kann ich eine PHP-Präsentation auf CD brennen? 3.23. [316]Werden meine PHP-Seiten von einer Suchmaschine indiziert? 3.24. [317]Wie kann ein Besucher meiner Seite den PHP-Code im Browser sehen? 3.25. [318]Gibt es für PHP einen "Dokumentationsgenerator" ähnlich Javadoc für Java? 3.26. [319]Wie kann ich die Ausgabe meines Scriptes in einen anderen Frame umlenken? 3.27. [320]Warum ist es schlecht, mit dem Referer zu arbeiten? 3.28. [321]Für welche Zwecke ist der Referer zu gebrauchen? 3.29. [322]Kann ich PHP-Dateien kompilieren und so vor Dritten schützen? 3.1. Wie vergleicht sich PHP mit anderen bekannten Webentwicklungssystemen? | [323]Perl | [324]Java | [325]ASP | [326]Cold Fusion | [327]Webobjects | [328]Scriptsprache | [329]Vergleich | [330]Performance | [331]Server | Antwort von [332]Kristian Köhntopp PHP ist in Version 3 eine reine Interpretersprache, in Version 4 ein Bytecode-Compiler, der das Script beim Aufruf compiliert. PHP kann als CGI-Program oder als Bestandteil des Webservers ( mod_php ) ausgeführt werden. Als CGI-Programm ist es beliebig portabel, als Modul ist es für eine Reihe von populären APIs verfügbar (Apache, Netscape, Microsoft und fhttpd). PHP unterstützt Datenbankzugriffe nicht nur über ODBC (oftmals Treiber von schlechter Performance und mit unvollständigen API-Implementierungen), sondern auch über die Native API einer ganzen Reihe von Datenbanken. Dazu LDAP, IMAP und eine Reihe anderer Datenbanken, außerdem HTTP, FTP-Protokolle (Intelligent Agent-Programmierung) und Direktzugriff auf Dbase und DB/DBM-Dateiformate. Dynamische Generierung von PNG-Bildern mit der libgd und der freetype library. Volltextindices und Suchmaschinen über externe Open Source Programme (htdig und andere). Zahlreiche Spracherweiterungen vorhanden, der Quelltext des Interpreters liegt vor und ist Open Source. Die Syntax folgt der C, Java, Javascript, Perl-Familie von Sprachen. Ausgezeichnete Dokumentation und glänzender Support, wie bei Open Source üblich (Mailinglisten in Deutscher und Englischer Sprache). Sessionmanagement ab Version 4 eingebaut, davor Bestandteil von [333]PHPLIB . Cold Fusion ist eine Interpreter-Markup-Language. Es handelt sich um eine reine Interpretersprache mit der Option, Seitenquelltext auf der Serverseite durch "Codierung" zu verschleiern (das ausgegebene HTML muß selbstverständlich lesbar sein). Cold Fusion kann als Modul in Apache ausgeführt werden oder als CGI-Programm. Da es nicht Open Source ist, ist seine Verfügbarkeit über Plattformgrenzen beschränkt, Linux-Support ist angekündigt und im Betatest; Solaris wird ebenfalls unterstützt. Datenbankzugriff erfolgt über ODBC. LDAP, IMAP und einige andere Dinge werden unterstützt, andere Protokolle müssen ggf. über Dritthersteller oder Erweiterungen von Herstellerseite zugekauft werden. Intelligent Agents können erstellt werden. Keine Generierung von GIF-Bildern on-the-fly. CF kann Texte mit einer eigenen Engine (Verity) volltextindizieren und dann darauf zugreifen. Zahlreiche Spracherweiterungen vorhanden (Custom Tag Library beim Hersteller). Syntax: Tagschreibweise, um bei Anfängern den Eindruck einer Programmiersprache zu vermeiden. Dieser Eindruck wird unterstützt von CF-Studio, einer Spielversion von Homesite, die um teilweise lächerlich wirkenden Insert-tag Support für die CF-Standardtags erweitert wurde. Microsoft ASP ist keine Scriptsprache, sondern ein Framework für die Einbindung einer solchen. Es stellt mehr eine API dar, die beliebige Scriptsprachen verwenden können, um Variablen über die Lebensdauer einer Seite hinaus persistent zu machen, um mit dem Webserver und anderen Scriptsprachen kommunizieren zu können und um andere, Microsoft-spezifische Eigenheiten ansprechen zu können. Mit MS-ASP werden zwei Scriptsprachen mitgeliefert (JavaScript und VisualBasic), aber es existieren weitere, von Microsoft unabhängige Sprachen (etwa Perl), die ebenfalls ASP verwenden. ASP ist fester Bestandteil des IIS auf NT bzw. Windows 2000/XP, es gibt aber auch andere Webserver (z.B. [334]Sambar Server ), die ASP unterstützen. Da es sich beim IIS mit ASP um ein Microsoft-Produkt handelt, sind Supportoptionen, -preise und -Qualität sofort klar ( Hinweis: Seit Windows 2000 verlangt Microsoft eine zusätzliche [335]Internet Connection License für den IIS, wenn dieser am Internet betrieben werden soll). Unterstützung anderer Plattformen ist kaum möglich, da ASP starken Gebrauch von den OLE/COM/DCOM-Fertigkeiten der MS-Plattform macht: Selbst nach der Portierung (etwa durch Chilisoft oder Software AG) steht man immer noch vor dem Problem, die entsprechenden OLE/COM/DCOM-Objekte auf der Nicht-Microsoft Plattform verfügbar zu machen. LDAP, IMAP und andere Dinge sind über den OLE/COM/DCOM-Mechanismus möglich, entsprechende Objekte sind eventuell von Drittanbietern zu kaufen. mod_perl und embedPerl sind ein Mechanismus, mit denen man den Perl-interpreter als Bestandteil des Apache-Webservers ausführen kann und mit dem man dann Perl-Programme mit HTML mischen kann. Programme werden proaktiv geladen und compiliert, sodaß die typische Startup-Latenz von Perl-Programmen als CGI wegfällt. Der Speicherverbrauch ist beträchtlich, die Geschwindigkeit ist größer als bei PHP Version 3 - PHP Version 4 und mod_perl liegen in etwa gleichauf. Unterstützt wird der Apache Webserver auf allen Plattformen. mod_perl ist prinzipiell in der Lage, alle Perl-Module auf dem CPAN auszuführen, daher werden alle von Perl unterstützten Datenbanken (einschließlich ODBC und einer Menge nativer Interfaces) unterstützt, ebenso LDAP, IMAP, LibGD. Syntax folgt der von Perl... Negativ fällt hier vor allen Dingen der übermäßige Speicherverbrauch bei größeren Projekten auf und die auf Anfänger abschreckend wirkende Vielfalt von Sprachkonstrukten und Bibliotheken. Besondere Pluspunkte für erfahrene Programmierer sind die Ausdrucksstärke und Vielfalt der Sprache und ihrer Konstrukte und die wirklich umfassende Sammlung von Bibliotheken. Next/Apple Webobjects sind ein System, mit dem ein kleines, im Source vorliegendes Rumpf-C-CGI-Programm über einen remote procedure call mit einem als Coprozess laufenden Anwendungsprogramm kommuniziert. Das Anwendungsprogramm ist mit dem Webobjects Toolkit in Objective-C geschrieben und wird dann compiliert, es ist ein Maschinenprogramm. Kommunikation erfolgt durch das Datenbank-Kit von Next über die Native API der unterstützten Datenbanken mit beliebigen Datenbanken. Next bietet außerdem eine exzellente Entwicklungsumgebung mit GUI-Designer (ja, erzeugt auch HTML :-) und einem sehr schönen ER-Modeller und Debugger (gdb mit einem sexy Outfit). Webobjects skaliert sich ausgezeichnet durch das verwendete RPC-Schema (Next Portable Distributed Objects auf einem beliebigen etablierten RPC-Mechanismus aufsetzend) und die Möglichkeit, den Application Server zu replizeren. Interessant ist die ungeschlagene Geschwindigkeit und die extrem gute Entwicklungsumgebung. 3.2. Wie vergleicht sich die Performance von PHP zu Perl? | [336]Performance | [337]Perl | [338]Vergleich | [339]Zend | [340]Compiler | [341]Interpreter | Antwort von [342]Kristian Köhntopp Eine Zählschleife in PHP3 ist um etwa den Faktor 10 langsamer als dieselbe Zählschleife in Perl, weil PHP3 die Zeilen der Schleife bei jedem Durchlauf erneut parsed und dann ausführt, während Perl beim Start des Interpreters Bytecode erzeugt und dann ausführt. Typische Programme bestehen nicht aus Zählschleifen, sondern aus komplexen Funktionen, mit denen man das eigentliche Ziel des Programmes bewirken will. Solche Funktionen sind in Perlprogrammen in der Regel in perl realisiert, während diese Funktionen in PHP3 eher in C geschrieben sind. C ist in solchen Fällen etwa um den Faktor 5-10 schneller als Perl. Bytecode-Compiler haben außerdem eine Startup-Zeit, die ebenfalls in die Laufzeit des Programmes mit eingeht. Wer vorne liegt, hängt extrem von der Aufgabe und dem verwendeten Code ab. Im Zweifel kann man in beiden Sprachen die entsprechende Funktion als Spracherweiterung in C implementieren und ist so in jedem Fall schneller als mit einer PHP- oder Perlfunktion. PHP3 und PHP4 liegen hier wegen des im Vergleich zu Perl5 sehr viel besser strukturierten Sourcecodes vom Entwicklungsaufwand deutlich vorne. Ein Perl-Projekt, das den Perl-Interpreter neu schreiben will, ist derzeit in Arbeit. PHP4 erzeugt wie Perl beim Start des Programmes Bytecode und führt diesen dann aus. Dabei liegt PHP4 mit Perl geschwindigkeitsmäßig in etwa gleichauf. Ebenso wie Perl braucht auch PHP4 dafür eine größere Startup-Zeit, in der das Programm analysiert und übersetzt wird. Der Mitte März zum Betatest freigegebene Zend-Optimizer optimiert den PHP4-Bytecode noch einmal und holt je nach Anwendung noch einmal Geschwindigkeitsgewinne heraus. Diese Gewinne können in Benchmarks sehr signifikant sein - ihre praktische Bedeutung wird ebenfalls merkbar sein, aber sicherlich nicht so extrem wie in den Benchmarks. Die Anwendung des Optimizers erhöht die Startup-Zeiten des PHP4-Interpreters noch weiter. Der Optimizer ist ein closed source Produkt und wird nach Abschluss der Beta-Phase käuflich zu erwerben sein. Der noch freizugebende Zend-Compiler kann den PHP4-Bytecode oder den optimierten PHP4-Bytecode speichern. Der Interpreter kann diesen Bytecode dann laden und sofort (ohne weitere Startup-Kosten fuer die Übersetzung) ausführen. Außerdem können in Bytecode übersetzte Programme an Kunden herausgegeben werden, ohne daß der Kunde einen Blick in den Source werfen kann. Der Compiler wird ein closed source Produkt sein und nach Abschluss der Beta-Phase käuflich zu erwerben sein. Der noch freizugebende Zend-Cache kann häufig verwendeten Bytecode erkennen und im Interpreter im Speicher halten. Der Interpreter braucht diesen Bytecode dann nicht mehr zu laden, zu analysieren und zu übersetzen, sondern kann ihn direkt aufrufen. Durch den Zend-Cache erhöht sich der Speicherverbrauch des Interpreters stark und bei unzureichendem Speicherausbau der Maschine kann sich das negativ auf die Performance des Gesamtsystems auswirken. Der Zend-Cache wird ein closed source Produkt sein und nach Abschluss der Beta-Phase käuflich zu erwerben sein. Man sagt, daß PHP3 und PHP4 leichter zu erlernen sind als Perl und daß PHP-Code leichter zu lesen und damit billiger zu warten sei als Perl-Code. Das ist sicherlich eine Frage der Übung - man kann in beiden Sprache nicht mehr wartbare Programme entwickeln bzw. in beiden Sprachen selbstdokumentierenden Code abliefern. In PHP ist die Versuchung, kryptischen Code abzuliefern möglicherweise etwas kleiner als in Perl, aber speziell PHP3 ist hier in einigen Punkten behindert, weil es in der Sprache Referenzen (Zeiger auf Variablen) keine Objekte erster Ordnung sind und nur sehr eingeschränkt verwendet werden können (nur bei der Parameteruebergabe an Funktionen) und daher manchmal einige ekelige Workarounds notwendig machen. Dieses Problem ist erst in PHP4 behoben. 3.3. Wie kann ich mein ASP-Programm in PHP übersetzen? | [343]ASP | [344]Kompatibilitaet | [345]Umstieg | [346]uebersetzen | [347]Konverter | Antwort von [348]Kristian Köhntopp Mit Hilfe des Konverters [349]asp2php kann man große Teile seiner ASP-Anwendung zunächst einmal in PHP übersetzen lassen. In vielen Fällen ist kleinere Nacharbeit oder wenigstens Nachkontrolle notwendig. Viele Eigenschaften des ASP-Framework lassen sich mehr oder weniger direkt in PHP abbilden: ASP Application Object PHP hat kein Application Object, aber die Funktionalität läßt sich etwa mit Hilfe der Shared Memory und Semaphore-Funktionen leicht nachbilden. ASP Request Object Das ASP Request-Objekt gibt Zugriff auf Client-Zertifikate, Cookies, Formularvariablen, den Query-String und verschiedene Servervariablen. In PHP greift man auf Cookies als globale Variablen zu, oder über das Array $HTTP_COOKIE_VARS . Formularvariablen werden ebenfalls in den globalen Namensraum importiert oder sie sind in den Arrays $HTTP_GET_VARS und $HTTP_POST_VARS abgreifbar. Der Query-String und die Servervariablen sind ebenfalls globale Variablen oder können über [350]getallheaders() erreicht werden. Die Ausgabe von [351]phpinfo() gibt einen sehr schönen Überblick. ASP Response Object Das ASP Response Object hat einen Slot für Cookies, die Eigenschaften Buffer, ContentType, Expires, ExpiresAbsolute und Status sowie die Methoden AddHeader, AppendToLog, BinaryWrite, Clear, End, Flush, Redirect und Write. PHP Version 3 kennt keine Pufferung und daher auch keine Clear-Methode zu Löschen des Puffers. Stattdessen arbeitet man einfach mit einem beliebigen String, an den man mit Code wie __________________________________________________________ $buf .= sprintf("My Output
"); $buf .= sprintf("Goes here
"); __________________________________________________________ anhängt und den man dann entweder löschen oder ausgeben kann: __________________________________________________________ print $buf; # Flush $buf = ""; # Clear __________________________________________________________ In PHP Version 4 gibt es den Output-Buffer, in den alle Ausgaben des Interpreters laufen, wenn dies durch die Konfigurationsvariable [352]output_buffering eingeschaltet wird. Mit Hilfe der Funktionen [353]ob_start() , [354]ob_end_flush() (entspricht Flush) und [355]ob_end_clean() (entspricht Clear) kann man den Puffer kontrollieren, mit Hilfe von [356]ob_get_contents() bekommt man eine Kopie des Puffers zurückgegeben und kann ihn im Programm weiter manipulieren. In PHP kann man Header und Cookies nur solange erzeugen wie noch kein Text ausgegeben wurde. Cookies werden mit der [357]setcookie() -Funktion erzeugt. HTTP-Headerzeilen erzeugt man stattdessen mit [358]header() -Funktion. Diese kann eingesetzt werden, um den Content-Type, den Status, die Expires-Zeit oder beliebige andere Eigenschaften der Seite zu manipulieren und kann auch Redirects erzeugen. __________________________________________________________ # Content Type setzen header("Content-Type: image/gif"); # Expires bestimmen $exp_gmt = gmdate("D, d M Y H:i:s", time()+$exp) . " GMT"; header("Expires: " . $exp_gmt); # Status setzen header("Status: 401 Unauthorized"); # Redirect erzeugen header("Location: http://..../.../inded.html"); __________________________________________________________ Lognachrichten erzeugt in PHP die Funktion [359]syslog() . BinaryWrite entfällt, weil PHP-Funktionen binärsicher sind - man kann eine gewöhnliche Ausgabefunktion verwenden (siehe auch die Parameter rb und wb bei [360]fopen() . Das Äquivalent zu End ist [361]exit() . ASP Server object Das ASP Server Objekt hat die Eigenschaft ScriptTimeout und die Methoden CreateObject, HTMLEncode, MapPath und URLEncode. In PHP kann man die Laufzeit eines Scriptes und den Speicherverbraucht mit Hilfe der Konfigurationsvariablen [362]max_execution_time und [363]memory_limit begrenzen. Zum Codieren und Decodieren bietet PHP eine reichhaltige Familie von Funktionen und anders als ASP auch die passenden Decode-Funktionen. Die wichtigsten sind: [364]rawurlencode() , [365]rawurldecode() , [366]urlencode() , [367]urldecode() , [368]base64_encode() , [369]base64_decode() , [370]quoted_printable_decode() , [371]htmlspecialchars() , [372]htmlentities() , [373]addslashes() , [374]stripslashes() und [375]quotemeta() . ASP verwendet CreateObject, um COM-Objekte zu erzeugen und zu steuern. In Unix gibt es kein COM (aber eine Reihe von PHP-Erweiterungen, die genau die Funktionalität der COM-Objekte hhaben). In Windows NT erlaubt PHP Version 4 mit den Funktionen [376]com_load() , [377]com_invoke() , [378]com_propget() und [379]com_propput() den Zugriff auf COM-Objekte. Außerdem ist PHP Version 4 in der Lage, Objekteimplementationen zu überladen, sodaß man COM-Objekte wie native PHP-Objekte verwenden kann. Verwendet man PHP als Apache-Modul, kann man die Funktion MapPath durch [380]apache_lookup_uri() nachbilden, die jedoch noch sehr viel mehr Information zurückliefert als nur das Path-Mapping. In jedem Fall kann man sich für die eigene URL mit den Variablen $PATH_INFO und $PATH_TRANSLATED behelfen. ASP Session object Das ASP Session Objekt hat die Eigenschaften SessionID und Timeout und die Methode abandon. In PHP Version 3 mit PHPLIB verwendet man $sess->id für die Session-ID und $sess->lifetime für den Timeout. $sess->delete() entspricht abandon. In ASP speichert man Werte in seiner Session mit Hilfe von __________________________________________________________ Session("memo") = "remember me" __________________________________________________________ In PHPLIB schreibt man __________________________________________________________ $memo = "remember me"; $sess->register("memo"); __________________________________________________________ Die Funktionalität von Session_OnStart wird in PHPLIB mit Hilfe der Sessioneigenschaft $auto_init nachgebildet. PHPLIB hat außerdem Uservariablen. Diese gibt es nicht in ASP. 3.4. CGI PHP oder Modul? | [381]SAPI | [382]CGI | [383]Modul | [384]mod_php | [385]Installation | [386]shell | [387]cron | [388]cronjob | [389]crontab | [390]Performance | [391]suexec | Antwort von [392]Kristian Köhntopp Siehe auch [393]Webserver verstehen und tunen von Kristian Köhntopp. Jedesmal, wenn eine Seite mit PHP-Code darauf ausgeführt werden muß, muß der PHP-Interpreter gestartet werden. Wird PHP als CGI-Programm installiert, bedeutet dies, daß am Anfang der Seite ein neuer Prozeß erzeugt werden muß und der PHP-Interpreter in diesen Prozeß geladen werden muß. Dies verbraucht eine ganze Menge Systemressourcen. Am Ende der Seite endet auch der Interpreterprozeß und aller Speicher wird freigegeben. Ebenso werden alle Filehandles geschlossen und damit alle Datenbankverbindungen aufgegeben. Installiert man PHP dagegen als Modul, etwa in einem Apache-Webserver, dann ist das PHP-Modul Bestandteil des Webservers und ständig geladen. Es kann außerdem Datenbanklinks über die Lebensdauer einer PHP-Seite hinaus offen halten, was speziell bei Oracle-Datenbanken große Performancevorteile bringt. Nur mit einem CGI-PHP ist es möglich, PHP als Scriptsprache zur Erstellung von "Shellscripten" einzusetzen. Viele Massen-Webhoster setzen bevorzugt CGI-PHP ein, weil es sich leichter auf eine Weise installieren läßt, die die Systemsicherheit nicht gefährdet. CGI-PHP erlaubt den Einsatz der Apache-Erweiterung suexec , die Erstellung einer chroot() -Umgebung und die bessere Kontrolle der durch den Anwender verbrauchten Systemressourcen. Praktisch alle großen PHP-Sites setzen PHP als Modul ein, weil die Performance hier deutlich besser ist. 3.5. PHP-Scripte von Windows nach Unix portieren? | [394]Windows | [395]Unix | [396]Linux | [397]Betriebssystem | [398]Portierung | [399]Plattform | [400]Wechsel | Antwort von [401]Kristian Köhntopp PHP ist weitgehend unabhängig von der verwendeten Systemplattform. Von den Funktionen microtime() und crypt() ist bekannt, daß sie sich beiden Betriebssystemen unterschiedlich verhalten. Einige PHP-Module stehen nicht unter allen Systemplattformen zur Verfügung. So ist das Posix-Modul systembedingt nur unter Unix sinnvoll einsetzbar und das DCOM-Modul nur unter Windows verfügbar. 3.6. Welche Editoren sind für PHP geeignet? | [402]Editor | [403]Entwicklungsumgebung | [404]Windows | [405]Unix | [406]Mac | [407]Syntax | [408]Projekt | [409]Verwaltung | [410]HTML | [411]PHP | [412]Debugger | Antwort von [413]Kristian Köhntopp Prinzipiell ist selbstverständlich jeder ASCII-Editor für PHP geeignet. Eine generische Liste in englischer Sprache wird von Keith Edmunds auf [414]PHP Editors gepflegt. Folgende Editoren (Auswahl) bieten spezielle Unterstützung für PHP (z.B. Syntax-Hervorhebung): In Unix (alphabetisch): * [415]Emacs oder [416]XEmacs mit dem [417]PHP-Modus und [418]weiteren Tools Die aktuellen Versionen (Emacs ab 20.5, XEmacs ab 21.2) unterstützen sog. indirect buffers, die sich ihren Text mit einem anderem, dem "base buffer" teilen, aber eigene major modes, lokale Variablen, extents und narrowing haben. Auf diese Weise bietet (X)Emacs volle Unterstützung für mehrere Programmiersprachen (HTML, PHP, Perl) innerhalb einer Datei. Das Lisp-Paket [419]mmm.el (Multiple Major Modes for XEmacs) bietet in XEmacs diese Unterstützung mehrerer Sprachen innerhalb eines buffers an. Eine Anleitung, um Lisp für PHP in einem HTML-Umgebung zu integrieren, beschreibt Martin Glinski auf seiner [420]Homepage * [421]Bluefish is a GTK HTML editor for the experienced web designer * ASPN [422]Komodo ist eine integrierte Entwicklungsumgebung mit Debugger * [423]NEdit mit [424]PHP Highlighting * [425]Nexidion ist eine PHP IDE für den K Desktop. Der Vorteil dieser IDE liegt darin, dass zum ersten Mal ein frei erhältlicher Debugger und Profiler für PHP mitgeliefert wird * [426]Quanta Plus is an HTML editor for the K Desktop * [427]Vim im PHP-Modus. Hilfe über Syntax-Highlighting ist mit :help syntax abrufen. Highlighting kann mit :syntax on eingeschaltet werden. Es gibt Syntax-Schemata für HTML und PHP In Windows (alphabetisch): * [428]Dreamweaver 3 * [429]Emacs für Windows * Homesite von [430]Allaire . Der Expression-Builder für PHP und Homesite: [431]http://hshelp.com/mrspecial.html#ebphp * ASPN [432]Komodo ist eine integrierte Entwicklungsumgebung mit Debugger * [433]Maguma PHP4EE Studio Light * [434]MED * [435]PHPEd * [436]PHPCoder * [437]PHP Script Editor * [438]Proton Code Editor von Ulli Meybohm * WebAuthor von [439]Resoft * [440]Textpad * [441]Ultraedit * [442](g)vim für Windows * [443]XEmacs für Windows Für den Apple Mac (alphabetisch): * [444]BBEdit ist ein kommerzielles Produkt von BareBones Software und kostet ca. $130. Es bietet Syntax Coloring für PHP (seit Version 6), C, C++, Java, Perl, HTML, TeX, JavaScript, Object Pascal, Fortran, Tcl und andere. Von Carsten Blüm gibt es einen [445]Patch , um das Syntax Coloring auf die Funktionen der stets aktuellen PHP-Version zu erweitern * [446]JaneBUILDER for PHP ist Shareware und kostet in der Vollversion $49 * [447]Pepper von [448]Hekkelman ist Shareware und kostet ca. $45. Der Editor orientiert sich an BBEdit, ist aber insbesondere beim Syntax Coloring stärker und bietet im HTML-Modus auch Coloring für PHP-Konstrukte. Das Coloring ist durch den Benutzer erweiterbar * [449]Vim im PHP-Modus ist auch für den Apple Mac erhältlich Manche Editoren können sehr gut mit Microsoft Active Server Pages arbeiten, verändern aber PHP Code. In diesem Fall ist es sinnvoll, in der php.ini die Konfigurationsvariable [450]asp_tags zu aktivieren und seinen Code mit Hilfe von <% ... %> zu schreiben. Speziell für Frontpage und ältere Versionen von Dreamweaver ist dies hilfreich. 3.7. Zeitgesteuerte PHP-Scripte und Shellscripte | [451]cron | [452]cronjob | [453]crontab | [454]CGI | [455]Nachts | [456]Regelmaessig | [457]Zeit | [458]ausfuehren | [459]Script | [460]at | [461]Task | Antwort von [462]Kristian Köhntopp "Hello, World!" als PHP-Script "hello.php": _________________________________________________________________ #! /usr/local/bin/php -q -- _________________________________________________________________ Dies setzt eine installierte CGI-Version von PHP in /usr/local/bin/php voraus. Ein solches Script läßt sich über die Unix-Zeitsteuerung cron regelmäßig aufrufen. Dem Script steht das Array $argv[] zur Verfügung, welches die Kommandozeilenparameter des Aufrufs enthält. Dieses Array kann auf die übliche Weise aufgezählt werden. Die Anzahl der Elemente des Arrays kann man mit der Funktion [463]count() bestimmen oder in der Variablen $argc nachschlagen. Hat man nur mod_php zur Verfügung, kann man eine bestimmte URL des Webservers durch PHP regelmäßig zeitgesteuert abrufen lassen. Dazu sind Tools wie wget (Suse Linux Paket wget in Serie n1 ) oder lynx (Suse Linux Paket lynx in Serie n1 ) hilfreich. 3.8. Wie bette ich PHP in HTML ein? (Beispielprogramm) | [464]HTML | [465]Script | [466]Delimiter | [467]parsen | [468]Frontpage | [469]Dreamweaver | [470]tag | [471]PHP | [472]Bereich | [473]Anfang | [474]Ende | Antwort von [475]Kristian Köhntopp PHP-Code wird mit Hilfe von SGML Processing Instructions (PIs) in HTML eingebettet. Der Code steht zwischen : _________________________________________________________________ _________________________________________________________________ Alternativ kann auch ein Script-Tag für Server-Side Scripting verwendet werden: _________________________________________________________________ _________________________________________________________________ Falls die Konfigurationsvariable [476]short_open_tag gesetzt ist, kann man den Namen der Scriptsprache in den PIs weglassen: _________________________________________________________________ _________________________________________________________________ Falls die Konfigurationsvariable [477]asp_tags gesetzt ist, kann man auch _________________________________________________________________ <% phpinfo(); %> _________________________________________________________________ a la Microsoft schreiben. Dies wird von vielen Editoren (Frontpage, Dreamweaver) besser verstanden. Außerdem steht einem dann das Kürzel <%= $somevar %> zur Verfügung: _________________________________________________________________ <%= $PHP_SELF %> <% echo $PHP_SELF %> _________________________________________________________________ 3.9. Wie finde ich heraus, wie mein PHP-Interpreter konfiguriert ist? | [478]Konfiguration | [479]Interpreter | [480]Information | [481]phpinfo | Antwort von [482]Kristian Köhntopp Die PHP-Funktion [483]phpinfo() liefert eine Fülle von Information über die aktuelle Konfiguration des Interpreters. Das typische Testprogramm sieht so aus: _________________________________________________________________ _________________________________________________________________ 3.10. Wo finde ich die php.ini? | [484]Konfiguration | [485]Interpreter | [486]Information | [487]php.ini | [488]Pfad | Antwort von [489]Johannes Frömter Die Ausgabe von [490]phpinfo() sagt unter anderem, wo PHP4 seine Konfigurationsdatei php.ini sucht. Steht hier nur php.ini , hat PHP keine Datei gefunden und arbeitet mit Default-Einstellungen (diese entsprechen etwa denen aus der php.ini-dist ); weniger irreführend ist die Ausgabe von echo get_cfg_var('cfg_file_path') , sie ist in diesem Fall einfach leer. Bei der Kompilierung von PHP kann mit der Direktive --with-config-file-path=/dir ein Verzeichnis definiert werden; default-mäßig erwartet PHP die php.ini unter Unix in /usr/local/lib und unter Windows im Windows-Verzeichnis. Dorthin kopiert man die mitgelieferte php.ini-dist und benennt sie in php.ini um. Neben der php.ini und den .htaccess -Dateien gibt es unter Windows auch die Möglichkeit, in der Registry PHP-Konfigurations-Einstellungen vorzunehmen - ob dies der Übersichtlichkeit dienlich ist, sei dahingestellt... Die Einstellungen wirken sich als 'local value' (siehe [491]phpinfo() ) auf bestimmte Verzeichnisse sowie deren Unterverzeichnisse aus. 1. Regedit öffnen 2. Den Schlüssel HKEY_LOCAL_MACHINE\SOFTWARE öffnen 3. Den Schlüssel PHP anlegen 4. Den Schlüssel Per Directory Values anlegen 5. Einen Schlüssel für das Laufwerk anlegen, in dem sich das Document Root befindet, z.B. C oder D (ohne ' :\ '!) 6. Einen Schlüssel für das Document-Root-Verzeichnis anlegen, z.B. ' www ' 7. (Für jedes Unterverzeichnis einen weiteren Unterschlüssel anlegen, z.B. ' php ') 8. Einen neuen "Zeichenfolgenwert" anlegen, z.B ' auto_prepend_file ' 9. Dem neuen Eintrag einen Wert zuweisen, z.B. ' prepend.php ' Der vollständige Schlüssel aus obigem Beispiel sollte nun so aussehen: HKEY_LOCAL_MACHINE\SOFTWARE\PHP\Per Directory Values\C\www\php -> auto_prepend_file = 'prepend.php' . Anmerkung: Egal ob der Eintrag ein bool'scher Wert, Integer oder String ist, der Schlüssel muß immer vom Typ "Zeichenfolgenwert" (engl. "string") sein. 3.11. Wie kann ich auf Umgebungsvariablen zugreifen? | [492]Umgebungsvariable | [493]getenv | [494]Referer | [495]Variable | Antwort von [496]Kristian Köhntopp Man kann Umgebungsvariablen über die PHP-Einbaufunktion [497]getenv() eine solche Variable lesen und sie mit Hilfe der PHP-Einbaufunktion [498]putenv() setzen. Dies ist die empfohlene Methode. Alternativ sind Umgebungsvariablen sind innerhalb von PHP als Variablen im Array $HTTP_ENV_VARS verfügbar. Die Umgebungsvariable HOME steht also als Element im Array $HTTP_ENV_VARS["HOME"] zur Verfügung. Wenn innerhalb einer Funktion auf den Wert einer solchen globalen Variablen zugegriffen werden soll, darf man nicht vergessen, dieses Array als global zu deklarieren. Hinweis: In PHP3 stehen diese Variablen noch als globale Variablen zur Verfügung. Dies ist ein Sicherheitsrisiko. In PHP4 stehen diese Variablen im Array $HTTP_ENV_VARS zur Verfügung, und als globale Variablen, wenn die Einstellung [499]register_globals in der php.ini aktiviert ist. Es ist empfohlen, [500]register_globals auf Off zu stellen. _________________________________________________________________ <% function somefunc() { # Empfohlen echo getenv("HOME"). "
\n"; # So geht es auch. global $HTTP_ENV_VARS; echo $HTTP_ENV_VARS["HOME"]
\n"; } somefunc(); %> _________________________________________________________________ Mit Hilfe der Funktion [501]phpinfo() bekommt man unter anderem auch eine Übersicht über alle diese Variablen. 3.12. Wie kann ich auf den HTTP-Request-Header zugreifen? | [502]Query-String | [503]Request | [504]http | [505]Header | [506]Variable | Antwort von [507]Kristian Köhntopp Allen CGI-Programmen stehen zusätzliche Zeilen des HTTP-Request-Headers als Umgebungsvariablen zur Verfügung. Die Headerzeile bla-fasel: laber wird dabei zur Umgebungsvariablen HTTP_BLA_FASEL mit dem Wert laber . Zusätzliche Angaben hinter der URL der Seite stehen in der Umgebungsvariablen PATH_INFO zur Verfügung. So wird im Request http://example.com/test.php3/additional/info der Anteil /additional/info in dieser Variablen zu finden sein. Zusätzliche Angaben in der Form von Query-Strings wie sie von Formularen mit der METHOD=get erzeugt werden, stehen in der Variablen QUERY_STRING zur Verfügung. Ist der Query-String korrekt formuliert, werden diese Parameter auch als globale Variablen vorbelegt. So wird der Request http://example.com/test.php3?a=b&c=d die Umgebungsvariable QUERY_STRING mit dem Wert a=b&c=d belegen und außerdem die globale Variable $a mit dem Wert b und die globale Variable $c mit dem Wert d vorbelegen. Die Interpretation von Query-Strings wird außerdem noch durch die Konfigurationsparameter [508]gpc_order und [509]track_vars in der globalen Konfigurationsdatei php3.ini gesteuert. Verwendet man PHP als Apache-Modul, so kann man außerdem noch über die spezielle Funktion [510]getallheaders() auf diese Request-Header zugreifen. Ein solches Script ist jedoch nicht mit einem CGI-Interpreter ausführbar, daher sollte man diese Funktion im Namen der Portabilität vermeiden. 3.13. Gibt es noch mehr interessante Variablen im Environment? | [511]Umgebungsvariable | [512]getenv | [513]Referer | [514]Browser | [515]IP | [516]HTTP | Antwort von [517]Kristian Köhntopp Der [518]CGI-Standard definiert eine Reihe von Variablen, die vorhanden sein müssen. Eine Übersicht bekommt man wie üblich mit [519]phpinfo() Von besonderem Interesse sind HTTP_REFERER Die URL der Seite, die auf diese Seite verwiesen hat. HTTP_USER_AGENT Die Browserkennung des abrufenden Browsers. REMOTE_ADDR Die IP-Nummer des abrufenden Rechners. Dies kann die momentane IP-Nummer des tatsächlichen Abrufers (oft dynamisch vergeben und variabel) oder die IP-Nummer des Proxy-Servers sein, den der Abrufer verwendet. 3.14. Ich verwende PHP (Version 3) als Apache-Modul. Wie kann ich dies konfigurieren? | [520]Apache | [521]php.ini | [522]Konfiguration | [523]htaccess | [524]Modul | [525]mod_php | Antwort von [526]Kristian Köhntopp Immer wenn von einer Konfigurationsanweisung der Form name=wert für die Konfigurationsdatei php3.ini die Rede ist, dann kann für mod_php in Apache stattdessen auch die Apache-Konfigurationsdirektive php3_name wert verwendet werden. Man beachte, daß dem Namen der Anweisung php3_ vorangestellt wird und daß in einer Apache-Konfigurationsdatei keine Gleichheitszeichen verwendet werden dürfen. Die Konfigurationsdirektiven können in der httpd.conf in einem -Block oder in einer .htaccess -Datei stehen. Sie gelten für das bezeichnete Verzeichnis und alle seine Unterverzeichnisse. Dies erlaubt es, die PHP-Konfiguration pro Verzeichnis anzupassen. Mit Hilfe der PHP-Funktion [527]get_cfg_var.php() lassen sich Konfigurationsvariablen aus der php3.ini zur Laufzeit abfragen. 3.15. Was bedeuten master value und local value in phpinfo()? | [528]php.ini | [529]Konfiguration | [530]master | [531]local | [532]value | [533]Modul | [534]Server | Antwort von [535]Kristian Köhntopp Die Unterscheidung hat nur in der Modulversion von PHP einen Sinn: Der Master Value ist der Wert, der in der php3.ini (PHP4: php.ini ) eingetragen ist. Diese Datei wird nur beim Neustart des PHP-Interpreters (beim Modul also beim Neustart des Webservers) eingelesen und beachtet. Diese Werte gelten überall auf dem Server, wenn kein besonderer local value definiert ist. Der Local Value ist der Wert, der in diesem Verzeichnis gilt. Er kann in einem Apache -Block oder in einer .htaccess -Datei eingetragen sein. In ersterem Fall wird er beim Neustart des Servers neu gelesen, in letzterem Fall wird er bei jedem Zugriff neu gelesen und beachtet. 3.16. Welche Konfigurationsvariablen kann ich nicht in .htaccess-Dateien verwenden? | [536]Konfiguration | [537]htaccess | [538]php.ini | Antwort von [539]Kristian Köhntopp Sicherheitsrelevante Konfigurationseinträge sind nicht über .htaccess -Dateien steuerbar, sondern nur über Einträge in oder -Blocks in der zentralen Konfigurationsdatei. Sonst wären sie auch sinnlos, da die .htaccess -Dateien ja durch den Anwender kontrolliert werden. Aus mod_php3.c::php3_commands[] : _________________________________________________________________ {"php3_safe_mode", php3flaghandler, (void *)4, ACCESS_CONF|RSRC_CONF, FLAG, "on|off"}, _________________________________________________________________ Aus /usr/local/httpd/include/http_config.h : _________________________________________________________________ /* The allowed locations for a configuration directive are the union * of those indicated by each set bit in the req_override mask. * * (req_override & RSRC_CONF) => *.conf outside * or * (req_override & ACCESS_CONF) => *.conf inside * or * (req_override & OR_AUTHCFG) => *.conf inside * or and .htaccess * when AllowOverride AuthConfig * (req_override & OR_LIMIT) => *.conf inside * or and .htaccess * when AllowOverride Limit * (req_override & OR_OPTIONS) => *.conf anywhere and .htaccess * when AllowOverride Options * (req_override & OR_FILEINFO) => *.conf anywhere and .htaccess * when AllowOverride FileInfo * (req_override & OR_INDEXES) => *.conf anywhere and .htaccess * when AllowOverride Indexes */ _________________________________________________________________ Wie man sieht, ist die Anweisung php3_safe_mode nur in der httpd.conf erlaubt, nicht jedoch in .htaccess -Dateien. Die folgenden Anweisungen sind sicherheitsrelevant und können nicht in einer .htaccess -Datei verwenden werden, sondern müssen direkt in den Server konfiguriert werden: * php3_doc_root * php3_user_dir * php3_safe_mode_exec_dir * php3_upload_tmp_dir * php3_extension_dir * php3_sendmail_path * php3_safe_mode * php3_sql_safe_mode * php3_engine * php3_enable_dl * php3_ignore_user_abort * php3_expose_php Sicherheitshinweis: php3_sendmail_path ist erst seit PHP 3.0.15 als sicherheitsrelevant gekennzeichnet. In kleineren Versionen von PHP kann die Konfigurationsanweisung in .htaccess -Dateien verwendet werden und ist geeignet, aus dem safe_mode auszubrechen und um beliebige Kommandos unter der UID des Webservers auszuführen: PHP versendet in Unix eine Mail dadurch, daß die [540]mail() -Funktion die Unix-Funktion popen() mit dem in php3_sendmail_path konfigurierten Kommando aufruft. Konfiguriert man jetzt ein beliebiges Kommandos mit php3_sendmail_path in seiner .htaccess -Datei und ruft dann [541]mail() auf, dann wird PHP dieses Kommando starten und die Mail auf der Standardeingabe reinpipen. Das Kommando läuft unter der UID des Webservers (bei einigen nicht richtig schlauen Webhostern also als root) und stellt dann die Mail zu oder auch nicht und macht noch beliebige andere Dinge. Webhoster, die PHP3 mit [542]safe_mode einsetzen, sollten dringend auf die aktuelle PHP3-Version updaten. 3.17. Was genau bewirkt safe_mode und ist das sicher? | [543]safe_mode | [544]Sicherheit | [545]Konfiguration | [546]php.ini | [547]Beschraenkung | [548]gefaehrlich | Antwort von [549]Kristian Köhntopp Es gibt eine Konfigurationsvariable [550]safe_mode , die in der php3.ini gesetzt werden kann. Weiterhin gibt es die Konfigurationsvariablen [551]safe_mode_exec_dir und [552]sql_safe_mode . Wenn [553]safe_mode aktiv ist, sind verschiedene PHP3-Funktionen privilegiert oder eingeschränkt. Zumeist gilt die Einschränkung safe_mode Einschränkung , daß auf eine Datei oder ein Verzeichnis nur eingewirkt werden darf, wenn die Datei oder das Verzeichnis denselben Eigentümer hat wie das Script. Im einzelnen: * Alle Dateifunktionen einschließlich [554]include() und [555]require() können nur noch mit lokalen Dateien arbeiten, die denselben Eigentümer (uid) haben wie der Eigentümer des Scriptes. * + Auch ftp-Pfadnamen werden so geprüft. + Auch zlib-Pfadnamen werden so geprüft. + [556]mkdir() und [557]posix_mkfifo() sind nur in Verzeichnissen zugelassen, die die Einschränkung erfüllen. + Das Ziel eines [558]link() und [559]symlink() (Parameter 1 von [560]symlink() ) muß die Einschränkung erfüllen. + Die Subjekte der Funktionen [561]unlink() , [562]chgrp() , [563]chown() , [564]chmod() , [565]touch() , [566]rmdir() , [567]rename() und [568]copy() müssen die Einschränkung erfüllen. * Lokalen, absoluten Pfadnamen wird document_root vorangestellt. * In den Funktionen [569]popen() , [570]system() und [571]exec() können nur Kommandos ausgeführt werden, die im [572]safe_mode_exec_dir stehen. Auf die ausgeführten Kommandos wird von den Funktionen [573]popen() , [574]system() und [575]exec() automatisch [576]EscapeShellCmd() angewendet. * Dynamisch ladbare Erweiterungen ( [577]dl() -Funktion) stehen in [578]safe_mode nicht zur Verfügung ("Dynamically loaded extensions aren't allowed when running in SAFE MODE."). * Es kann kein neues CPU-Zeitlimit festgelegt werden ("Cannot set time limit in safe mode"). * Wird mod_php verwendet, steht in der Funktion [579]getallheaders() der Wert des Headers "Authorization" (Loginname und Passwort) nicht zur Verfuegung. * Die Funktion [580]header() wird dem Header "WWW-authenticate" immer den Eigentümer des Scriptes als Bestandteil des Realms hinzufügen. * Das filepro Map-File "dir/map" (wobei dir der Parameter der filepro()-Funktionen ist) muß die Einschränkung erfüllen. Dies gilt fuer [581]filepro() , [582]filepro_rowcount() und [583]filepro_retrieve() . * Das dbase-Modul verlangt in [584]dbase_open() und [585]dbase_create() , daß die dbf-Datei die Einschränkung erfüllt. * Das DBM-Modul setzt voraus, dass in [586]dbmopen() das Verzeichnis, in dem die DBM-Datenbank angelegt wird, die Einschränkung erfüllt. Dies ist bei den DBA-Funktionen nicht der Fall. Wenn [587]sql_safe_mode aktiv ist, können bei einem MySQL-Connect host , user und password nicht angegeben werden ("SQL safe mode in effect - ignoring host/user/password information"). safe_mode ist nicht sicher: Ein Fehler in der [588]popen() -Funktion ist erst mit 3.0.14 korrigiert worden, ein weiterer Fehler in der [589]mail() -Funktion erst in 3.0.15. Man sollte stattdessen die CGI-Version in einem chroot -Environment verwenden und mit setrlimit noch weitergehende Einschränkungen definieren. 3.18. Was ist --enable-force-cgi-redirect? Warum enthält $PHP_SELF den Pfad zum CGI-Interpreter? | [590]Sicherheit | [591]CGI | [592]PHP_SELF | [593]Pfad | [594]php.exe | [595]Interpreter | [596]URL | Antwort von [597]Kristian Köhntopp Wenn CGI-PHP eingesetzt wird, bekommt der Interpreter den Pfad zum PHP3-Script in der CGI-Variablen $PATH_INFO übergeben. Der Aufruf eines PHP-Scriptes erfolgt also mit Hilfe einer URL der Form _________________________________________________________________ http://www.example.com/cgi-bin/php/somedir/somescript.php3 _________________________________________________________________ Der PHP-Interpreter wird dabei unter der URL http://www.example.com/cgi-bin/php erreicht und die Variable $PATH_INFO erhält den Wert /somedir/somescript.php3 . Der PHP-Interpreter greift sich den Wert der Konfigurationsvariablen [598]document_root aus der php3.ini und setzt den Dateinamen des Scriptes aus dieser Variablen und $PATH_INFO zusammen. Dies ist natürlich gefährlich, denn ein böswillger Anwender könnte jetzt anfangen, Scripte wie folgt aufzurufen: _________________________________________________________________ http://www.example.com/cgi-bin/php/../../../../../etc/passwd _________________________________________________________________ Der PHP-Interpreter würde nun anfangen, die passwd-Datei als Script zu laden und auszuführen, d.h. die Paßwort-Datei auszugeben. Auf diese Weise kann man prinzipiell auf jede Datei im System zugreifen, sofern diese durch den Benutzer lesbar ist, unter dessen UID der Interpreter abläuft. Übersetzt man CGI-PHP mit der Konfigurationsoption enable-force-cgi-redirect , funktioniert dies nicht mehr. PHP startet in diesem Fall nur noch, wenn die CGI-Umgebungsvariable $REDIRECT_STATUS gesetzt ist (der Wert der Variablen ist egal). Der Apache Server setzt diese Variable, wenn der Zugriff auf das CGI-Verzeichnis die Folge eines eines serverinternen Redirect ist. Üblicherweise konfiguriert man seinen Webserver dann so: _________________________________________________________________ Action php3-script /cgi-bin/php AddHandler php3-script .php3 _________________________________________________________________ Die Direktive AddHandler bildet die Endung .php3 auf den Handler php3-script (der Name ist frei wählbar, solange er eindeutig ist) ab. Die Direktive Action deklariert eine externe Aktion, die durch ein CGI-Programm realisiert wird. In Kombination bewirken beide Direktiven, daß alle Zugriffe auf Dateien mit der Endung .php3 auf das CGI-Programm /cgi-bin/php redirected werden. Dies erzeugt als Nebenwirkung genau die benötigte $REDIRECT_STATUS -Variable. Die beiden Apache-Konfigurationsdirektiven bewirken, daß Dateien mit der Endung .php3 im PHP-Interpreter landen und die Abfrage auf die $REDIRECT_STATUS -Variable bewirkt, daß ein direkter Aufruf des PHP-Interpreters mit einem manipulierten $PATH_INFO nicht mehr möglich ist. In Kombination stellen beide Mechanismen ein vernünftiges Maß an Sicherheit her. PHP berechnet nun den Wert der Variablen $PHP_SELF auf andere Weise, falls enable-force-cgi-redirect in Kraft ist: In diesem Fall enthält die Variable wirklich nur den Namen des Scriptes ohne den Pfad zum PHP-Interpreter. Ist der Interpreter jedoch ohne diese Einstellung übersetzt worden, ist der Name des PHP-Interpreters Bestandteil von $PHP_SELF . Dies ist ein Hinweis auf ein schweres Sicherheitsproblem. Der Interpreter sollte ausgetauscht werden durch eine Version, die mit enable-force-cgi-redirect übersetzt wurde. Manche Webserver sind nicht in der Lage, Endungen auf CGI-Programme zu mappen (beim Netscape Server kann man dies mit Hilfe eines Plugin-Modules nachrüsten) oder sie erzeugen keine $REDIRECT_STATUS -Variable, wenn sie ein solches Mapping vorgenommen haben. In diesem Fall muß man ohne enable-force-cgi-redirect arbeiten und mit dem Sicherheitsloch leben oder - besser - den Webserver wechseln. 3.19. Warum funktioniert set_time_limit() nicht wie angepriesen? | [599]set_time_limit | [600]Dauer | [601]Abbruch | [602]Maximum | [603]Zeit | [604]Ausfuehrung | Antwort von [605]Kristian Köhntopp Die Funktion [606]set_time_limit() bzw. die Konfigurationsanweisung [607]max_execution_time in der php3.ini wirkt nicht auf die absolute Laufzeit des Scriptes, sondern sie begrenzt die verbrauchte CPU-Zeit eines Scriptes. In _________________________________________________________________ set_time_limit(1); sleep(10); print("hallo"); _________________________________________________________________ verbraucht die [608]sleep() -Funktion zwar reale Zeit, aber keine CPU-Zeit. Daher wird das Zeitlimit von einer Sekunde hier auch nicht wirksam und der Text wird noch gedruckt. 3.20. Wie kann ich auf Kommandozeilen-Argumente zugreifen? | [609]shell | [610]Parameter | [611]CGI | [612]Kommandozeile | [613]Script | Antwort von [614]Kristian Köhntopp Wenn PHP über die Shell als Skriptsprache benutzt wird, ist es oft nützlich, auf der Kommandozeile Parameter zu übergeben. In PHP stehen die Variablen $argc und $argv zur Verfügung. $argc Anzahl der auf der Kommandozeile übergebenen Argumente. $argv Array mit den übergebenen Argumenten und dem Dateinamen im ersten Element. Wenn ein PHP-Skript über das Web aufgerufen wird, enthalten diese Variablen die über GET übergeben Argumente. In PHP 4.0 kann man dieses Verhalten in der php.ini -Datei abschalten ( [615]register_argv_argc ). Beispiel: _________________________________________________________________ tobias@dev:~ > cat arg.php3 #!/usr/bin/php -q tobias@dev:~ > ./arg.php3 foo bar baz ./arg.php3 foo bar baz _________________________________________________________________ 3.21. Wie kann ich einen Parameter von einer PHP-Seite an eine andere weitergeben? | [616]get | [617]post | [618]http | [619]Parameter | [620]urlencode | [621]Sonderzeichen | [622]Codierung | Antwort von [623]Kristian Köhntopp Man kann Parameter an ein PHP-Script als HTTP-GET- oder HTTP-POST-Parameter übergeben. Die Übergabe von Parametern als HTTP-POST ist in [624]Wie kann ich einen HTTP POST-Request absenden? erläutert. Einen HTTP-GET-Request erzeugt man, indem man einfach ein Link auf das gewünschte Script erzeugt und die Parameter mit der Funktion [625]urlencode() codiert anhängt. _________________________________________________________________ go _________________________________________________________________ Das empfangende Script wird diese Parameter ganz normal entgegennehmen, automatisch decodieren und als globale Variablen mit den Namen $para1 und $para2 bereitstellen. Die Länge der durch einen GET-Request übergebaren Parameter ist begrenzt. Im einem GET- oder POST-Request übergebene Parameter sind durch den Anwender leicht manipulierbar. Wie in [626]Webserver verstehen und tunen diskutiert, ist es wesentlich besser, Sessionvariablen zu verwenden, wie sie etwa durch [627]PHPLIB oder mit Hilfe der [628]Sessionfunktionen von PHP4 realisiert sind - im Gegensatz zu dem hier gezeigten manipulierbaren Verfahren sind Sessions nämlich sicher. 3.22. Wie kann ich eine PHP-Präsentation auf CD brennen? | [629]CD | [630]Diskette | [631]Vorfuehrung | [632]Praesentation | [633]Server | [634]Installation | Antwort von [635]Kristian Köhntopp PHP ist zur Ausführung auf einen Webserver angewiesen. Es ist zwar möglich, eine PHP-Präsentation mittels eines Spiders durchzugehen und das generierte HTML abzuspeichern, aber die Interaktivität und die Datenbankverbindungen gehen so verloren. Alternativ kann man dem Kunden einen Webserver mitliefern, den dieser dann auf seinem Rechner installieren muß und der dann die PHP-Scripte ausführt. Der Kunde wird dann unter der URL http://localhost/ auf die Anwendung zugreifen können. Antwort von [636]Johannes Frömter Von [637]IndigoSTAR Software gibt es den Webserver [638]MicroWeb , der direkt von CD-ROM unter Windows gestartet wird. Man kann einen beliebigen Hostnamen definieren und per HTTP auf die Daten zugreifen (z.B. http://microweb/ ). Standardmäßig bringt MicroWeb Perl- und SSI-Unterstützung mit, man kann aber auch PHP (als CGI) einbinden (siehe Dokumentation). Natürlich sind auf der CD keine Datenbank- und Dateioperationen möglich; für letztere besorgt man sich am besten via getenv("TEMP") das temporäre Verzeichnis des Rechners und arbeitet damit auf der Festplatte. 3.23. Werden meine PHP-Seiten von einer Suchmaschine indiziert? | [639]Suchmaschine | [640]PHP | [641]Index | [642]indiziert | [643]dynamisch | [644]Meta-Tag | Antwort von [645]Kristian Köhntopp Ein Webspider bekommt bei der üblichen Konfiguration von Webservern in keinster Weise mit, ob die angeforderte Default-Datei (Directory-Index) eines Verzeichnisses nun home.htm, index.php3 oder irgendwie anders heißt. Er wird diese Datei lesen, und dann werden die HTML-Tags extrahiert, die für das Ranking aber noch eine Rolle spielen können (

...

). Aus dem reinen Text werden meistens noch die Stopwörter entfernt und restliche Textbrei fließt dann aufbereitet in den Index. Die Meta-Tags spielen bei intelligenteren Suchmaschinen für das Ranking meist kaum noch eine Rolle. Jede Suchmaschine könnte grundsätzlich dynamisch generierte Seiten genauso erfassen, wie statische Seiten, weil der Spider/Robot der Suchmaschine genauso ein Client ist, wie Dein Browser und nicht mehr und nicht weniger sieht als Dein Browser: nämlich den HTML-Code und den Content-Type. Endungen der Dateinamen spielen bei korrekt programmierten Suchmaschinen keine Rolle - entscheidend sind im Web stattdessen die übermittelten Content-Types. Viele Suchmaschinenbetreiber werden jedoch keine dynamisch generierten Seiten erfassen, weil sie davon ausgehen, daß sich deren Inhalt sehr oft ändert und eine Indizierung der Seiten somit sinnlos ist. Wird nun bei einem HTML-Dokument aufgrund der Extension, des Pfadnamens (enthält das Schlüsselwort cgi ) oder offensichtlich per GET übergebener Parameter eine dynamische Generierung vermutet, werden diese Dateien von einigen Spidern nicht indiziert, bzw. entsprechende Links nicht verfolgt. Dies ist zwar ebenfalls falsch - stattdessen sollte sich der Spider nur nach dem Inhalt der Datei robots.txt richten - aber viele Sites haben nur ungenügende oder ganz fehlende robots.txt-Dateien. Lies auch den [646]Artikel von Tobias Ratschiller in der [647]Suchfibel zu diesem Thema. 3.24. Wie kann ein Besucher meiner Seite den PHP-Code im Browser sehen? | [648]Code | [649]Sicherheit | [650]Passwort | [651]Quelltext | [652]Browser | [653]Server | Antwort von [654]Martin Jansen Wenn der Webserver ordnungsgemäß konfiguriert ist und er PHP unterstützt, bekommt der Besucher der Seite den PHP-Code nicht zu sehen, da er vom Webserver geparst wird und nur der generierte HTML-Code an den Browser geschickt wird. Folgende Szenarien sind jedoch denkbar, bei denen der Besucher den PHP-Code trotzdem zu sehen bekommt: * Auf dem Webserver ist kein PHP-Parser installiert. In diesem Fall behandelt der Webserver den PHP-Code wie "normalen" HTML-Code und schickt ihn unbearbeitet (ergo ungeparst) an den Browser. * Aufgrund einer Fehlfunktion des Webserver ist der PHP-Parser ausgefallen oder der Webserver benutzt ihn nicht mehr: In diesem Fall sieht der User ebenfalls den ungeparsten Source-Code im Browserfenster, da kein Parsing mehr stattfindet. * Ein "böser Junge" verschafft sich per FTP Zugriff auf die Daten: Auch hier hat der User die Möglichkeit, den Source-Code einzusehen, da der PHP-Parser nur Dateien parst, die über den Webserver abgefragt werden. Andere Schnittstellen wie zum Beispiel FTP berücksichtigt er nicht. * Ein User verschafft sich Zugriff auf den Webserver mit Telnet oder SSH: Auch hier kann er den PHP-Code ohne weiteres einsehen, indem er zum Beispiel cat irgendwas.php eingibt und der Inhalt der Datei irgendwas.php auf dem Bildschirm ausgegeben wird. Siehe auch: [655]Kann ich PHP-Dateien kompilieren und so vor Dritten schützen? In jedem Fall ist es sinnvoll, Code mit Datenbankpaßworten in einem Verzeichnis abzulegen, das nicht durch eine URL erreichbar ist oder das mit einer .htaccess-Datei gegen Zugriff gesichert ist. Dieser Code kann dann mit Hilfe von [656]include() oder [657]require() eingebunden werden. Siehe dazu auch den Abschnitt [658]Wie kann ich mein Datenbankpaßwort gegen Spionage sichern? dieser FAQ. 3.25. Gibt es für PHP einen "Dokumentationsgenerator" ähnlich Javadoc für Java? | [659]Javadoc | [660]PHPDoc | [661]Dokumentation | [662]Kommentar | [663]Quelltext | Antwort von Guido Haeger Im Moment gibt es noch kein Standard-Tool für PHP, mit dem sich aus dem Quellcode Dokumentationen generieren lassen. Es sind jedoch momentan mehrere diesbezügliche Projekte im Alpha- oder Beta-Stadium. U.a.: * [664]PHPDoc (von Ulf Wendel) * [665]PHPDoc (von Goeran Zaengerlein) * [666]PHPDoc (von ?) 3.26. Wie kann ich die Ausgabe meines Scriptes in einen anderen Frame umlenken? | [667]Frame | [668]target | [669]Fenster | [670]umlenken | [671]Ausgabe | [672]Pop-Up | Antwort von [673]Daniel T. Gorski Wenn das Script bereits läuft, gar nicht. Es gibt einen inoffiziellen Lösungsansatz seitens Netscape, mit dem es möglich ist, die Ausgabe eines laufenden Scriptes in einen anderen Frame umzulenken. Dies ist jedoch nicht portabel: PHP läuft auf dem Server und weiß zunächst einmal nichts von den Frames eines Clients. Es jedoch möglich, sich mit JavaScript eine Brückenseite zu schreiben (vom laufendem Script schreiben zu lassen), die mit Hilfe von onLoad() erneut eine PHP-Seite vom Server für einen anderen Frame anfordert ( top.anderesFrame.location.href="seite1.php"; self.location.href="seite2.php" ). Von diesem Verfahren wird hier deutlich abgeraten, da viele User (bei manchen Firmen gehört das auch zu der Firmenpolicy) JavaScript aus Sicherheitsgründen abschalten. Dann bleibt der Bildschirm u.U. leer und der Besucher verwirrt. Wiederkommen wird er bestimmt nicht mehr. Vor der Ausführung des Scriptes, ist es selbstverständlich möglich mittels das Script in einem anderem Frame (bzw. mit javascript:window.open() geöffneten Fenster) ausführen zu lassen. 3.27. Warum ist es schlecht, mit dem Referer zu arbeiten? | [674]Referer | [675]HTTP | [676]Browser | Antwort von [677]Martin Jansen Der Referer ist eine Variable, in der stehen soll, von welcher Seite der Benutzer kommt, der sich gerade auf der Seite befindet. Hat der User zum Beispiel bei einer Suchmaschine auf einen Link geklickt, so würde der Referer "http://suchmaschine.tld/query?suchwort=german-faq" lauten. Dies muss allerdings nicht immer so sein: * Die Entwickler des Browsers haben nicht vorgesehen, dass ein Referer-String mitgesendet wird oder der User hat das Mitsenden des Referers im Browser deaktivert. * Der User verwendet einen lokalen Proxyserver (z.B. [678]Webwasher ), der die Referer-Information herausfiltert. * Ein Proxyserver bei einem Provider, im Rechenzentrum einer Universität oder in einem Unternehmen ist so konfiguriert, dass er keine Referer-Strings mitsendet. * Neben dem kompletten Entfernen des Referer-Strings aus den Headerdaten kann es auch möglich sein, dass der Referer durch o.g. Quellen modifiziert wird und dadurch unbrauchbar wird. Diese Punkte sind Argumente dafür, den Referer nicht zu sicherheitsrelevanten Zwecken auf einer Website einzusetzen. 3.28. Für welche Zwecke ist der Referer zu gebrauchen? | [679]referer | [680]statistik | [681]download | [682]schuetzen | [683]HTTP | [684]Nutzen | Antwort von [685]Johannes Frömter Für Webserver-Statistiken zum Beispiel. Im Standard-Format des access.log von Apache wird der HTTP-Referer erfaßt (wenn der Browser einen liefert); bereiten Statistikprogramme diese Logfiles auf, können sie Aussagen über Klickpfade ("von welcher meiner Seiten wird am Häufigsten auf Seite xy gegangen?"), Einstiegsseiten ("auf welche Seiten kommen Besucher von außen häufig?"), Linkseiten ("welche andere Seiten linken mich?") oder fehlerhafte Links ("von welcher Seite kam der Besucher zu dem toten Link?") machen. Will man den Referer trotz der oben beschriebenen Gegenargumente für weitergehende Zwecke benutzen (z.B. zum Schutz eines Programmdownloades vor direkter Verlinkung), sollte man unbedingt so vorgehen, daß Requests mit fehlendem Referer nicht behindert werden. Der Besucher kann völlig schuldlos an einem fehlenden Referer sein, z.B. wenn er hinter einem Proxy in der Firma sitzt. Daher darf man den Zugriff nur dann abblocken, wenn der Referer vorhanden ist, aber einen nicht erlaubten Wert hat. Bei der umgekehrten Vorgehensweise (Zugriff nur bei Referer von der eigenen Seite) verscherzt man es sich unweigerlich mit einem Teil seiner Besucher. In PHP hat man über die Variable $HTTP_SERVER_VARS["HTTP_REFERER"] Zugriff auf den Referer. Die Funktion [686]parse_url() dürfte beim Zerlegen des Strings sehr hilfreich sein, damit man ihn einfacher vergleichen kann. Man sollte sich aber immer bewußt sein, daß der Referer keine 100%-ige Sache ist. Will (oder muß) man den Zugriff auf bestimmte Ressourcen "wasserdicht" gestalten, führt kein Weg an einer Session-Lösung vorbei. 3.29. Kann ich PHP-Dateien kompilieren und so vor Dritten schützen? | [687]compiler | [688]source | [689]code | [690]encoder | [691]Schutz | [692]Verschluesselung | Antwort von [693]Johannes Frömter Anders als beispielsweise bei Java wird PHP-Code erst zur Laufzeit (wenn das Script aufgerufen wird) kompiliert, d.h. der Programmcode liegt im "Klartext" in den .php -Dateien. Für normale Besucher einer von PHP erzeugten Webseite ist der Programmcode nicht einzusehen. Solange man die Scripte nur auf dem eigenen Webserver einsetzt, ist der Code für andere unsichtbar (siehe hierzu auch den Artikel ' [694]Wie kann ein Besucher meiner Seite den PHP-Code im Browser sehen? '). Gibt man PHP-Dateien jedoch (an Kunden) weiter, erhalten diese damit den Source Code. Wenn der Code nicht "Open Source" ist, sollte man per Vertrag das Copyright und die Nutzungsbedingungen genau regeln. Von [695]Zend gibt es als kommerzielles Produkt den [696]Zend Encoder Unlimited , der PHP-Code in ein binäres Format überführt und damit weitgehend vor Ausspähung, Veränderung und Re-engineering schützt. Das Produkt ist nicht gerade billig, außerdem braucht man natürlich auf jedem Webserver, der kompilierten Code ausführen soll, ein Zusatzmodul (dieses allerdings ist kostenlos). Nach dem gleichen Prinzip funktioniert der [697]PHP Bytecode Compiler , der im derzeitigen Beta-Stadium allerdings noch keinen objektorientierten Code verarbeiten kann. Die Kompilierung erfolgt außerdem "online", der Encoder ist (noch) nicht zum Download verfügbar. Auch [698]microCODE macht aus PHP-Code einen nicht lesbaren Bytecode, der durch ein einzubindendes .so-Modul vom PHP-Interpreter ausgeführt werden kann. Einen etwas anderen Weg gehen die Tools [699]Code Obfuscator und [700]POBS - sie kommen ohne serverseitige Module aus, da sie den Code nur für Menschen, nicht aber für den PHP-Interpreter "unleserlich" machen. Der Code Obfuscator ersetzt die (meist sprechenden) Variablennamen durch (nichtssagende) Konstrukte, so daß es schwer ist, die Funktion eines so bearbeiteten Scripts nachzuverfolgen. POBS macht dasselbe, geht aber noch weiter, indem auch Konstanten und Funktionsnamen verändert und Kommentare, Einrückungen sowie Leerzeilen entfernt werden. Der resultierende Code ist immer noch gültiger PHP-Code, aber seine Funktionsweise ist kaum mehr zu entziffern. 4. Typen und Funktionen 4.1. [701]Welche Variablenarten gibt es in PHP und wie greife ich auf sie zu? 4.2. [702]Welche Datentypen gibt es in PHP? 4.3. [703]Wie schreibe ich eine Funktion mit einer variablen Anzahl von Argumenten? 4.4. [704]Wie gebe ich mehrere Werte mit einer Funktion zurück? 4.5. [705]Wie schreibe ich ein Script, das beliebige Parameter verarbeitet? 4.6. [706]Variable Variablen 4.7. [707]Was ist der Unterschied zwischen isset() und einem Vergleich auf den Leerstring? 4.8. [708]Wie kann ich JavaScript-Funktionen aus PHP heraus aufrufen? 4.9. [709]Wie kann ich PHP-Funktionen aus JavaScript heraus aufrufen? 4.1. Welche Variablenarten gibt es in PHP und wie greife ich auf sie zu? | [710]Variable | [711]Konstante | [712]Namensraum | [713]Funktion | [714]Global | [715]Local | [716]Static | [717]Klasse | [718]Objekt | [719]Konstruktor | [720]Typ | Antwort von [721]Kristian Köhntopp Konstanten _________________________________________________________________ define(NAME, Wert); _________________________________________________________________ definiert eine Konstante. Diese Konstante ist überall gültig, also nicht nur im globalen Namensraum, sondern auch in Klassen und in Funktionen. Globale Variablen Eine globale Variable definiert man implizit durch ihre Benutzung innerhalb des globalen Namensraumes, also einfach durch _________________________________________________________________ $a = 10; _________________________________________________________________ außerhalb jeder Klasse oder Funktion. Variablen in Funktionen In PHP hat jede Funktion ihren separaten Namensraum. In diesem Namensraum existieren nur diejenigen Variablen, die als Formalparameter der Funktion in die Funktion importiert worden sind oder die innerhalb der Funktion definiert worden sind. Diese Variablen haben die Lebensdauer des Funktionsaufrufes, d.h. sie verlieren beim Verlassen der Funktion ihren Wert. Es ist möglich, in den Namensraum einer Funktion weitere Variablen hineinzuimportieren. Dies geschieht mit Hilfe der Anweisung global . Beispiel: _________________________________________________________________ function testfunc($para1) { global $glob1; $loc1 = 10; $glob1 = $glob1 + 1; return; } _________________________________________________________________ Diese Funktion hat zunächst einmal die Variablen $para1 und $loc1 als Parameter bzw. als lokale Variable definiert. Diese Variablen verlieren mit der schließenden Klammer "}" ihren Wert. Außerdem wird die Variable $glob1 in den Namensraum der Funktion importiert. Der Wert von $glob1 ist auch außerhalb von testfunc() sichtbar und $glob1 bleibt für die gesamte Lebensdauer des Programmes bestehen. Weiterhin kann man innerhalb einer Funktion noch lokale Variablen definieren, die nur innerhalb der Funktion sichtbar sind, deren Wert jedoch nach dem Ende der Funktion erhalten bleibt und bei einem erneuten Funktionsaufruf erneut sichtbar wird. Dies erfolgt mit Hilfe der Anweisung static . _________________________________________________________________ function testfunc2($para1) { global $glob1; static $stat1 = 0; $loc1 = 10; $glob1 = $glob1 + 1; $stat1 = $stat1 + 1; printf("Die Funktion testfunc2() wurde %s mal aufgerufen.\n", $stat1); } _________________________________________________________________ In diesem Beispiel ist die Anweisung static $stat1 dazu gekommen, die die Variable $stat1 als lokale, aber langlebige Variable in testfunc2() definiert und zur Vermeidung einer Warnung mit 0 initialisiert. Der Wert von $stat1 bleibt über einen einzelnen Funktionsaufruf erhalten. $stat1 zählt also im Beispiel oben die Anzahl der Aufrufe von testfunc2() . PHP definiert die Pseudovariable $GLOBALS[] vom Typ Array/Hash. Es handelt sich um einen Namen fuer die globale Symboltabelle des Interpreters. Der Name ist in allen Namensräumen sichtbar. Entsprechend ist das Konstrukt _________________________________________________________________ function testfunc3() { global $glob1; $glob1 = $glob1 + 1; return; } _________________________________________________________________ gleichbedeutend mit _________________________________________________________________ function testfunc3b() { $GLOBALS["glob1"] = $GLOBALS["glob1"] + 1; return; } _________________________________________________________________ Variablen in Klassen Die dritte Sorte Namensräume, die in PHP existiert, sind [722]Klassen und Objekte . Eine Klasse wird vereinbart mit der Anweisung _________________________________________________________________ class MyClass { var $a; var $b; function c() { print("Der Wert von a ist %s\n", $this->a) $this->b = $this->b + 1; print("Der Wert von b ist %s\n", $this->b); } function MyClass() { $this->a = 10; $this->b = 0; } } _________________________________________________________________ Nach dieser Definition ist KEINE Variable belegt, aber es existiert ein Bauplan fuer MyClass-Variablen. MyClass ist ein Typ, so wie integer, float oder string Typen in PHP sind. Mit Hilfe der Anweisung "new" kann man sich Variablen nach diesem Bauplan erstellen lassen. "new" ist also eine Fabrik, der man einen Bauplan mitgibt und die nach diesem Plan Variablen herstellt. _________________________________________________________________ $o = new MyClass; _________________________________________________________________ Den Bauplan einer Variablen bezeichnet man als Klasse, hier der Klasse MyClass. Die nach diesem Plan gebaute Variable als ein Objekt der Klasse, hier also als das Objekt $o der Klasse MyClass. Manche Leute sagen zu dem Objekt $o der Klasse MyClass auch " $o ist eine Instanz der Klasse MyClass". Nach einem Bauplan können mehrere Objekte gebaut werden, d.h. eine Klasse kann mehrere Instanzen haben. _________________________________________________________________ $p = new MyClass; _________________________________________________________________ Die Variablen mit den Namen "a" und "b" in einem Objekt der Klasse MyClass werden in PHP als Slots oder Instanzvariablen bezeichnet. Man spricht sie immer über den Namen der Klasse an: _________________________________________________________________ $o->a = 17; $p->a = 31; _________________________________________________________________ Dadurch werden sie unterscheidbar, d.h. es ist erkennbar, ob man den Slot mit dem Namen "a" des Objektes $o oder des Objektes $p meint. Das kann man sich vorstellen wie Pfadnamen: In unterschiedlichen Verzeichnissen eines Dateisystems kann man ja auch Dateien mit demselben Namen liegen haben, aber diese Variablen unterscheiden sich im Pfadnamen. Eine Klasse kann nicht nur Instanzvariablen definieren, sondern auch Instanzfunktionen, hier die Funktionen mit den Namen "c" und "MyClass". Sie werden genauso angesprochen wie die Instanzvariablen: _________________________________________________________________ $o->c(); $p->c(); $o->MyClass(); $p->MyClass(); _________________________________________________________________ Manche Leute nennen die Funktionen eines Objektes auch "Methoden" und reden dann von "Instanzmethoden" statt "Instanzfunktionen". Das hat keinen praktischen Zweck und dient nur der Förderung der allgemeinen Verwirrung. Beim Funktionsaufrufen gilt die folgende Sonderregel: Eine Funktion, die exakt denselben Namen hat wie die Klasse, in der sie enthalten ist, wird auch dann aufgerufen, wenn das Objekt von new gebaut wird. Diese Funktion kann also verwendet werden, um die Slots des Objektes mit Defaultwerten zu initialisieren: _________________________________________________________________ $c = new MyClass; _________________________________________________________________ Hier wird also die Funktion $c->MyClass() automatisch aufgerufen. Weil diese Funktion aufgerufen wird, während das Objekt nach dem Bauplan gebaut wird, nennt man eine solche Funktion einen Konstruktor. Ein Konstruktor könnte auch optionale Parameter erwarten und man könnte der Funktion dann beim Zusammenbau diese Parameter mitgeben: _________________________________________________________________ class MyClass2 { ... function MyClass2($p1 = "") { if ($p1 != "") { $this->... } } } $mc1 = new MyClass2("beispielwert"); $mc2 = new MyClass2(); _________________________________________________________________ Wenn der Bauplan einer Variablen erstellt wird, wenn wir also die Klasse MyClass definieren, dann wissen wir noch nicht, unter welchen Namen die Slots und Instanzfunktionen des Objektes einmal angesprochen werden. Daher können wir in der Funktion nicht $o->a oder $p->c() schreiben, sondern müssen irgendetwas allgemeineres als Platzhalter notieren. Die Pseudovariable $this steht dabei in einer Instanzfunktion als Platzhalter für den tatsächlichen Namen dieses Objektes. Innerhalb des Objektes $o steht $this->a also als Platzhalter fuer $o->a , innerhalb des Objektes $p steht $this->a jedoch als Platzhalter fuer $p->a . Man kann $this auch als "meine" lesen, also ist $this->a "mein Slot a" und $this->c() "meine Instanzfunktion c". Referenzen in PHP3 und PHP4 Schon in PHP3 ist es so, dass jede Zuweisung eines Kopie der angesprochenen Variablen erzeugt: _________________________________________________________________ $a = 10; $b = 20; $c = 30; $arr = array( $a, $b, $c ); $arr[1] = 22; _________________________________________________________________ Dies verändert den Wert von $arr[1] auf 22, aber der Wert von $b ändert sich nicht. Dies gilt auch bei Funktionsaufrufen: _________________________________________________________________ function m($p1) { $p1 = 22; } m($b); _________________________________________________________________ Dies setzt den Wert von $p1 auf 22, aber $p1 ist eine Kopie von $b und $b verändert den Wert nicht. Da beim Aufrufen ("Call") einer Funktion der Wert von $b in $p1 kopiert, also der Wert von $b an $p1 übergeben wird, nennt man dies "Call by value". Man könnte auch eine Funktion schreiben, die den Namen der Variablen übergeben bekommt und dann mit Hilfe dieses Namens auf die globale Variable dieses Namens zugreift: _________________________________________________________________ function m2($p1) { $GLOBALS[$p1] = 22; } m2("b"); _________________________________________________________________ Da in diesem Fall der Name der Variablen übergeben wird und mit Hilfe dieses Namens dann die originale Variable dieses Namens verändert wird, nennt man diese Art des Funktionsaufrufes "Call by name" (obwohl bei richtigem Call by Name noch viel obskurere Dinge möglich sind).. Schon in PHP3 ist es so, dass man eine Funktion als "Call by Reference" definieren kann. In diesem Fall wird keine Kopie des Variablenwertes übergeben, sondern der Formalparameter p1 wird zu einem alternativen Namen für den übergebenen Wert. Wieder wird der Wert der originalen globalen Variablen verändert: _________________________________________________________________ function m3(&$p1) { $p1 = 22; } m3($b); _________________________________________________________________ Hier ist $p1 in m3() ein alternativer Name fuer die Variable, die beim Aufruf genennt wird. In m3($b) wird die Variable $b benannt, also ist $p1 in diesem Aufruf ein alternativer Name fuer $b . Immer wenn innerhalb dieses Aufrufes von m3() $p1 verwendet wird, wird $b angesprochen. In PHP4 ist es nun neuerdings so, dass man Referenzen auch an anderer Stelle verwenden kann, zum Beispiel in der o.a. Arraydefinition: _________________________________________________________________ $a = 10; $b = 20; $c = 30; $arr = array( &$a, &$b, &$c ); $arr[1] = 22; print $b; _________________________________________________________________ Dieses Beispiel druckt den Wert 22. In diesem Beispiel sind $arr[0] , $arr[1] und $arr[2] keine Kopien der Werte von $a , $b und $c , sondern die $arr -Variablen werden zu alternativen Namen fuer $a , $b und $c . Jeder Zugriff auf $arr[1] spricht also in Wirklichkeit $b an. Man liest " $arr[1] = &$b " als " $arr von Eins ist eine Referenz auf $b ". Klassenfunktionen in PHP4 Wenn man eine Klasse definiert, kann man in PHP3 mit dieser Klasse nichts anderes machen als mit Hilfe von "new" Objekte dieser Klasse zu erzeugen. In PHP4 kann man jedoch nicht nur Funktionen eines Objektes aufrufen, sondern auch schon Funktionen einer Klasse. Das sind also nichts anderes als gewöhnliche Funktionsaufrufe, nur dass die Funktionsnamen sehr seltsam aussehen: _________________________________________________________________ class MyClass3 { function c() { print "Ich bin Funktion c() in der Klasse MyClass\n"; } } MyClass3::c(); _________________________________________________________________ Dieses Stück Code haette man so auch schreiben koennen und es wäre leichter zu verstehen gewesen: _________________________________________________________________ function c() { print "Ich bin die stinknormale Funktion c()\n"; } c(); _________________________________________________________________ Man kann dies verwenden, um in abgeleiteten Klassen Funktionen gleichen Namens in Oberklassen aufzurufen. 4.2. Welche Datentypen gibt es in PHP? | [723]Typ | [724]Variable | [725]Konstante | [726]Array | Antwort von [727]Kristian Köhntopp Dadurch, daß PHP allen Variablenbenutzungen das Markierungszeichen $ (Dollar) voranstellt, ist es möglich, Variablen in Stringkonstanten zu interpolieren, wie man es von der Unix-Shell her kennt. Will man das Markierungszeichen selbst ausgeben, kann man ihm es durch einen vorangestellten \ (Backslash) entwerten. Einen Backslash gibt man aus, indem man ihn entwertet: \\ (Backslash Backslash). In PHP gibt es die folgenden Datentypen: Skalare Werte: integer, float, string _________________________________________________________________ # Zuweisung $myint = 1; $myfloat = 3.14; $mystring= "hallo"; # Verwendung $yourint = $myint * 2; $yourfloat = $myfloat * 2.71828; $yourstring= $mystring . " du da!"; # Ausgabe print "$yourint\n"; print "$yourfloat\n"; print "$yourstring\n"; _________________________________________________________________ Felder (Arrays) Der Gebrauch von Feldern ist im Kapitel [728]Arrays und Arrayvariablen ausführlicher erläutert. PHP unterscheidet nicht zwischen Feldern (Arrays) mit Integer-Index und Hashes (Assoziativen Arrays) mit beliebigen Indices. _________________________________________________________________ # Arrays # Zuweisung $a1 = array( 10, 20, 30); $a2[0] = 10; $a2[2] = 30; $a2[1] = 20; # Verwendung $a3[0] = $a1[0] + $a2[2]; // $a3[0] ist 40 $a3[] = $a1[1] + $a1[0]; // $a3[1] ist 30, Index autom. vergeben # Ausgabe printf("A1: %s %s %s\n", $a1[0], $a1[1], $a1[2]); # Aufzählung for ($i=0; $i<=2; $i++) { printf("%s: %s\n", $i, $a1[$i]); } _________________________________________________________________ _________________________________________________________________ # Hashes # Zuweisung $a1 = array( "peter" => 10, "paul" => 20, "mary" => 30); $a2["peter"] = 10; $a2["mary"] = 30; $a2["paul"] = 20; # Verwendung $a3["peter"] = $a1["peter"] + $a2["mary"]; // $a3["peter"] ist 40 # Ausgabe printf("A1: %s %s %s\n", $a1["peter"], $a1["paul"], $a1["mary"]); # Aufzählung reset($a1); while(list($k, $v) = each($a1)) { print("%s: %s\n", $k, $v); } _________________________________________________________________ Objekte Der Gebrauch von Objekten ist im Kapitel [729]Klassen und Objekte ausführlicher erläutert. 4.3. Wie schreibe ich eine Funktion mit einer variablen Anzahl von Argumenten? | [730]Argument | [731]Funktion | [732]Array | Antwort von [733]Kristian Köhntopp In PHP kann man Funktionsparameter mit Default-Werten versehen. Läßt man die Argumente der Funktion von hinten nach vorne weg, werden stattdessen die Defaults eingesetzt. Defaultwerte müssen skalare Konstanten sein. Variable Ausdrücke (Variablen, Funktionsaufrufe) oder nichtskalare Werte (Arrays, Objekte) sind nicht gestattet. _________________________________________________________________ function beispiel($p = "default") { printf("Der Parameter p hat den Wert %s\n", $p); } beispiel("hallo"); beispiel(); _________________________________________________________________ Auf diese Weise kann man jedoch keine echten variadischen Funktionen schreiben. So ist es zum Beispiel nicht möglich eine Funktion wie [734]printf() in PHP3 zu schreiben. Man kann variadische Funktionen jedoch durch die Übergabe eines Array- oder Hashparameters simulieren. _________________________________________________________________ function beispiel2($p) { if (!isset($p) or !is_array($p)) # Defaults setzen $p = array("para1" => "bla", "para2" => "fasel"); if ($p["para1"]) machdies(); if ($p["para2"]) machdas(); } beispiel2(array("para1" => "laber", "para2" => "lall")); _________________________________________________________________ Echte variadische Funktionen sind erst in PHP4 möglich. Dort gibt es die drei Funktionen [735]func_num_args() Diese Funktion liefert die Anzahl der Funktionsargumente als Integer. [736]func_get_arg() Diese Funktion bekommt eine Argumentnummer als Parameter und liefert des Wert des Funktionsargumentes mit diesem Index zurück. [737]func_get_args() Diese Funktion liefert alle Argumente einer Funktion als Array zurück. Eine echte variadische Funktion kann also in PHP4 folgendermaßen geschrieben werden: _________________________________________________________________ function beispiel3() { $args = func_get_args(); for($i=0; $i _________________________________________________________________ 4.6. Variable Variablen | [749]Variable | [750]Array | [751]Global | Antwort von [752]Kristian Köhntopp Manchmal möchte man auf Variablen zugreifen, deren Namen variabel sind. Zum Beispiel könnte man die Variablen mit den Namen $myvar1 , $myvar2 , $myvar3 , ... , $myvar9 haben. Am günstigsten wäre es, in so einem Fall ein Array zu nehmen. _________________________________________________________________ for ($i=0; $i<10; $i++) echo $myvar[$i]; _________________________________________________________________ Wenn es unbedingt skalare Variablen sein müssen, kann man stattdessen über das $GLOBALS[] -Array zugreifen: _________________________________________________________________ for ($i=0; $i<10; $i++) { if (isset($GLOBALS["myvar$i"])) printf("Variable var%d existiert und ihr Wert ist %s
\n", $i, $GLOBALS["myvar$i"]); } _________________________________________________________________ Statt echo $GLOBALS[$lall]; kann man auch die zwei Befehle global $$lall; echo $$lall; verwenden. Empfohlen ist jedoch das Konstrukt mit $GLOBALS[] , weil es leichter zu lesen und zu verstehen ist. Das gilt besonders bei Dateinamen, die sich aus einem konstanten Stamm und einem variablen Anteil zusammensetzen. Eine weitere alternative Schreibweise für variable Variablen ist ${$lall}; für zusammengesetzte Variablennamen entsprechend beispielsweise ${"datei_$lall"}. 4.7. Was ist der Unterschied zwischen isset() und einem Vergleich auf den Leerstring? | [753]Variable | [754]Typ | Antwort von Georg Maaß if($var) evaluiert nur dann zu true , wenn $var keinen der folgenden Werte darstellt: false , 0 , 0.0 , "" oder "0" , NULL , array() . Alle diese Werte bedeuten false in ihrem jeweiligen Typ (Bool, Integer, Float, String, Null, Array). if(isset($weiter)) evaluiert immer zu true , wenn $weiter nicht undefined ist. Im ersten Fall wird der Inhalt, im zweiten Fall die Existenz der Variablen bewertet. Ein ähnliches Problem tritt bei Vergleichen auf: if($var == false) evaluiert immer dann zu true , wenn $var einen der obigen Werte darstellt. PHP führt hier eine automatische Typenkonvertierung durch, wodurch die beiden Variablen als äquivalent angesehen werden. 4.8. Wie kann ich JavaScript-Funktionen aus PHP heraus aufrufen? | [755]JavaScript | [756]Funktion | Antwort von [757]Johannes Frömter JavaScript läuft auf dem Client (im Browser), PHP läuft auf dem Server, also genau am anderen Ende der Welt; wenn die HTML-Seite beim Browser ankommt, ist PHP mit der Arbeit schon fertig. Der Aufruf einer JavaScript-Funktion aus PHP ist also prinzipiell unmöglich. Allerdings kann man Werte von PHP an JavaScript übergeben; um eine in PHP vorhandene Variable in JavaScript verwenden zu können, muß man sie innerhalb eines _________________________________________________________________ Auf diese Weise wird die JavaScript-Variable js_var mit dem Wert der PHP-Variable $php_var vorbelegt. Natürlich kann man so auch beliebigen ausführbaren JavaScript-Code ausgeben, den der Browser anschließend verarbeitet. Wichtig ist nur zu verstehen, daß ein logischer, räumlicher und auch zeitlicher Schnitt zwischen PHP- und JavaScript-Code vorhanden ist. 4.9. Wie kann ich PHP-Funktionen aus JavaScript heraus aufrufen? | [758]JavaScript | [759]Funktion | [760]GET | [761]POST | Antwort von [762]Johannes Frömter Da JavaScript auf dem Client und PHP auf dem Server läuft, kann man aus JavaScript auch keine PHP-Funktionen direkt aufrufen. PHP wird immer als das Resultat eines HTTP-Requests ausgeführt, also beim Holen einer Seite mit GET oder beim Verarbeiten eines Formulares mit POST. Es ist also nicht möglich, aus JavaScript heraus eine PHP-Funktion aufzurufen, außer durch Erzeugen eines HTTP-Requests (durch den von PHP eine neue Seite generiert wird). Einen GET-Request mit JavaScript erreicht man prinzipiell durch _________________________________________________________________ _________________________________________________________________ Im dadurch aufgerufenen PHP-Skript script.php ist dann die Variable $php_var mit dem Wert von js_var verfügbar. Einen POST- oder GET-Request mit einem Formular erreicht man durch _________________________________________________________________ _________________________________________________________________ Weitere Informationen zur Variablenübergabe: siehe [763]Variablen und Formulare in dieser FAQ. 5. Stringfunktionen 5.1. [764]Was ist besser, print() oder echo? 5.2. [765]Wie zerlege ich einen String? 5.3. [766]Wie zerlege ich eine URL? 5.4. [767]Wie gebe ich eine Zahl formatiert aus? 5.5. [768]Wie kann ich Zeilenumbrüche verarbeiten? 5.6. [769]Wie kann ich Zeilenumbrüche in
umwandeln? 5.7. [770]Wie breche ich einen String nach x Zeichen um? 5.8. [771]Wie kann ich einen String als PHP-Code ausführen? 5.1. Was ist besser, print() oder echo? | [772]Unterschied | [773]Vergleich | Antwort von [774]Kristian Köhntopp [775]echo() ist ein internes Sprachkonstrukt, [776]print() ist eine Expression. Man kann [777]print() also in Situationen benutzen, wo Expressions gefragt sind, z.B. $res = print("...")?1:0 . echo hat eine variable Argumentliste, dabei muß man aber auf die Klammern verzichten: echo $var1, $var2; . [778]print() kann nur ein Argument haben. In PHP 3 ist [779]echo() schneller, in PHP 4 ist die Geschwindigkeit gleich. Die Geschwindigkeitsdifferenz in PHP3 ist unter 3 Prozent. 5.2. Wie zerlege ich einen String? | [780]splitten | [781]teilen | [782]zerschneiden | [783]trennen | [784]Teilstring | Antwort von [785]Kristian Köhntopp Man kann einen String wie ein Array ansprechen: _________________________________________________________________ $str = "teststring"; $len = strlen($str); for($i=0; $i<$len; $i++) printf("Zeichen %d ist %s
\n", $i, $str[$i]); _________________________________________________________________ Mit Hilfe der Funktion [786]substr() kann man Teilstrings aus einem String herausschneiden. Mit Hilfe der Funktion [787]explode() kann man einen String an einem Trennzeichen in ein Array zerlegen. _________________________________________________________________ $str = "dies ist ein teststring."; $avar = explode(" ", $str); $len = count($avar); for ($i=0; $i<$len; $i++) printf("%d: %s
\n", $i, $avar[$i]); _________________________________________________________________ Dieses Beispiel zerlegt den gegebenen Teststring an den Leerzeichen und erzeugt ein Array $avar mit den Indices 0 bis 3 (4 Elementen). Kompliziertere Zerlegungen lassen sich mit Hilfe der Funktion [788]preg_split() vornehmen. Ältere Versionen von PHP3 haben diese Funktion nicht, dort muß man das weniger leistungsfähigere und langsamere [789]split() verwenden. _________________________________________________________________ $str = "ich bin ein sehr komplizierter test, nicht wahr?"; $avar = preg_split("/[ \t.!?]+/", $str); $len = count($avar); for ($i=0; $i<$len; $i++) printf("%d: %s
\n", $i, $avar[$i]); _________________________________________________________________ Im Gegensatz zum vorhergehenden Beispiel werden hier mehrfache Leerzeichen nicht als mehrfache Trennungen gezählt und auch Satzzeichen werden zu den Trennzeichen gezählt. 5.3. Wie zerlege ich eine URL? | [790]parsen | [791]Hyperlink | Antwort von [792]Kristian Köhntopp Mit Hilfe der Funktion [793]parse_url() kann eine URL in ihre Bestandteile zerlegt werden. _________________________________________________________________ $str = "http://user:password@www.koehntopp.de:80/kris/artikel#php"; $avar = parse_url($str); reset($avar); while(list($k, $v) = each($avar)) printf("k=%s, v=%s
\n", $k, $v); _________________________________________________________________ Ein QUERY_STRING kann mit Hilfe der Funktion [794]parse_str() in seine Variablen zerlegt werden. 5.4. Wie gebe ich eine Zahl formatiert aus? | [795]Komma | [796]Nachkommastellen | [797]Tausendertrennzeichen | Antwort von [798]Kristian Köhntopp Mit Hilfe der Funktion [799]number_format() oder mit Hilfe von [800]printf() . 5.5. Wie kann ich Zeilenumbrüche verarbeiten? | [801]String | [802]Zeilenumbruch | [803]Zeile | [804]Enter | Antwort von [805]Johannes Frömter Die sogenannten "Zeilenumbrüche" sind im Prinzip ganz gewöhnliche Bytes wie ein A oder % auch - sie werden erst bei der Anzeige in einem Editor als Zeilenumbrüche dargestellt. Häßlicherweise haben sich für diesen Zweck unterschiedliche Zeichen (Bytes) etabliert: Windows verwendet \r\n , Unix \n und der Mac \r als "neue-Zeile-Zeichen". Dabei gilt: _________________________________________________________________ Escape- Hex- ASCII- Abkür- Name/Bedeutung Name/Bedeutung Sequenz Code Code zung englisch deutsch ------- ---- ------ ------ ------------------ -------------- \r 0D 13 CR carriage return Wagenrücklauf \n 0A 10 LF line feed, newline Zeilenvorschub ------- ---- ------ ------ ------------------ -------------- _________________________________________________________________ Die Unterscheidung zwischen Wagenrücklauf und Zeilenvorschub rührt von den Zeilendruckern her, bei denen das zwei separate Steuersignale sind. Die Escape-Sequenzen können in PHP direkt in Strings, die zwischen " (Anführungszeichen, double quotes) stehen, sowie in Regulären Ausdrücken verwendet werden. Ebenso kann man dort \x0D , \x0A etc. (hexadezimale Escape-Sequenzen) verwenden. Die ASCII-Codes kann man von der Funktion [806]chr() umwandeln lassen. Empfängt man Daten von unbekannten Clients, sollten verarbeitende Funktionen mit allen Varianten zurecht kommen; eine universelle Funktion zum Ersetzen von Zeilenumbrüchen durch Leerzeichen sieht z.B. so aus: _________________________________________________________________ preg_replace('/\r\n|\r|\n/', ' ', $string); _________________________________________________________________ 5.6. Wie kann ich Zeilenumbrüche in
umwandeln? | [807]Zeilenumbruch | [808]Zeile | [809]HTML | Antwort von [810]Kristian Köhntopp PHP bietet die Funktion [811]nl2br() . Damit wird vor jeden Zeilenumbruch ein
(ab PHP 4.0.5 ein XHTML-konformes
) eingefügt. _________________________________________________________________ # Einlesen der Datei "datei" in den String $str $str = implode("", @file("datei")); # Ausgeben der Datei mit Umbrüchen print nl2br($str); _________________________________________________________________ Sollen Zeilenumbrüche komplett ersetzt werden, benutzt man [812]str_replace() : _________________________________________________________________ $string = str_replace("\n", "
", $string); _________________________________________________________________ Antwort von [813]Johannes Frömter Sollte man in die Verlegenheit kommen, eine Umkehrfunktion zu [814]nl2br() zu benötigen, muß man einen Regulären Ausdruck bemühen. _________________________________________________________________ function br2nl($str) { return preg_replace("=|([\s/][^>]*)>)\r?\n?=i", "\n", $str); } _________________________________________________________________ Der Ausdruck ist etwas länglich, weil er sicherstellt, daß wirklich nur
-Tags (die allerdings in allen Variationen!), nicht aber XML-Tags o.ä. ( z.B.) umgewandelt werden - sicher ist sicher... 5.7. Wie breche ich einen String nach x Zeichen um? | [815]Zeilenumbruch | [816]Zeilenlaenge | Antwort von [817]Johannes Frömter Ab PHP 4.0.2 gibt es die Funktion [818]wordwrap() , um lange Strings auf eine definierte Zeilenlänge zu bringen. Als Default wird nach 75 Zeichen mit \n umgebrochen, man kann aber optional als dritten bzw. vierten Parameter auch eigene Werte angeben, was gerade bei der Ausgabe in einer HTML-Seite praktisch ist: _________________________________________________________________ echo wordwrap($ganzLangerText, 25, "
", 1); _________________________________________________________________ Der vierte Parameter cut ist in PHP 4.0.3 hinzugekommen, er bewirkt, daß der String auf jeden Fall (auch mitten in einem Wort) umgebrochen wird. Achtung: Setzen Sie cut auf jeden Fall, sonst landet wordwrap() bei Wörtern mit einer Länge > width in einer Endlosschleife! Kann man wordwrap() nicht benutzen, helfen frei verfügbare Scripte wie z.B. [819]textwrap von Brian Moon. 5.8. Wie kann ich einen String als PHP-Code ausführen? | [820]String | [821]Code | [822]Datenbank | [823]Datei | [824]ausfuehren | Antwort von [825]Johannes Frömter Mit der Funktion [826]eval() kann man PHP-Code, der in einer Variablen als String vorliegt, ausführen lassen. eval() schaltet direkt in den PHP-Modus, d.h. PI-Tags (processing instructions, üblicherweise ) müssen im String nicht enthalten sein. Der String darf aber PIs enthalten, um vom PHP- in den HTML-Modus (und zurück) zu wechseln; beginnt der String mit HTML, so ist zuerst ein schließender PI-Tag nötig: _________________________________________________________________ $html_php_mix = '

'; eval("?> $html_php_mix lang und weilig noch eine zeile

Bla

tralalal"; preg_match_all("=]*>(.*)=siU", $str, $a); print $a[1][0]; _________________________________________________________________ Das Beispiel macht von den Optionen i , s und U der Perl Regular Expressions Gebrauch: Die Option i sorgt dafür, daß Groß- und Kleinschreibung keine Rolle spielen, die Option U sorgt dafür, daß Ungreedy gematched wird, d.h. der kürzest mögliche Match verwendet wird. Die Option s bewirkt, daß der Punktoperator auch Newlines mit matched. Dadurch ist es möglich, den regulären Ausdruck auf auf einen mehrzeiligen String anzuwenden. 6.8. Wie finde ich alle Links in einer HTML-Datei? | [883]Hyperlink | Antwort von [884]Björn Schotte $zeile sei der Inhalt einer zuvor eingelesenen HTML-Datei. Diese Variable muß innerhalb der While-Schleife neu zusammengebaut werden, sonst läuft man hier in eine Endlosschleife. _________________________________________________________________ $pattern = '=^(.*)]*)>(.*)
(.*)$=msi'; while (preg_match($pattern, $zeile, $txt)) { /* $txt[3] enthält die gewünschte URL. */ echo $txt[3]."\n"; /* $zeile neu bauen */ $zeile = $txt[1]." hier war mal ein Link ".$txt[6]; } /* $zeile zur Kontrolle ausgeben */ print "
".nl2br($zeile); _________________________________________________________________ $txt enthält als Array alle Tokens, die in der Regexp in Klammern angegeben sind. $txt[0] als Sonderstellung enthält den ganzen Text. 6.9. Wie ersetze ich alle relativen Links in einer HTML-Datei? | [885]Hyperlink | [886]relativ | [887]absolut | Antwort von [888]Björn Schotte $zeile sei der Inhalt einer zuvor eingelesenen HTML-Datei. Im folgenden Beispiel werden alle relativen Links durch das Konstrukt purl("relativerlink"); ?> ersetzt. relativerlink sei hierbei der relative Link, der gefunden wurde. Bei längeren Texten ist darauf zu achten, dass dieses Konstrukt teilweise recht lange dauert, bis es den kompletten Text durchsucht hat. _________________________________________________________________ print "Konvertiere"; flush(); $pattern = '=^(.*)\n"; flush(); } } print "Ersetzt:

".nl2br(htmlspecialchars($zeile)); _________________________________________________________________ 6.10. Wie überprüfe ich einen String auf seinen Inhalt? | [889]pruefen | [890]testen | Antwort von [891]Martin Jansen Häufig ist es nötig, festzustellen, ob ein String nur Ziffern bzw. nur Buchstaben enthält. $string sei die Zeichenkette, die überprüft werden soll. Die Regular Expression im ersten Beispiel überprüft, ob nur Ziffern in $string enthalten sind. Ist dies der Fall, gibt sie "Zeichenkette OK" aus, ansonsten lautet die Ausgabe "Ungültiges Zeichen in der Zeichenkette". _________________________________________________________________ /* Regex zur Ueberpruefung des Strings */ if (!preg_match("/^\d+$/",$string)) { echo "Ungültiges Zeichen in der Zeichenkette"; } else { echo "Zeichenkette OK"; } _________________________________________________________________ Um zu überprüfen, ob in der Zeichenkette nur Buchstaben stehen, kann man folgende Regex verwenden, die auf dem gleichen Prinzip beruht: _________________________________________________________________ if (!preg_match("=^[a-zäöüß]+$=i",$string)) { echo "Ungültiges Zeichen in der Zeichenkette"; } else { echo "Zeichenkette OK"; } _________________________________________________________________ 6.11. Wie ersetze ich in einem Text, jedoch nicht innerhalb von HTML-Tags? | [892]ersetzen | [893]begrenzen | Antwort von [894]Johannes Frömter Mit Regulären Ausdrücken kann man zwar wunderbar "positive Treffer" formulieren, aber das Gegenteil davon geht nur sehr schwer (abgesehen von negierten Zeichenklassen und Lookaheads/-behinds geht es nicht ). Die Entscheidung, ob ein Treffer innerhalb eines HTML-Tags (also zwischen < und > ) liegt oder nicht, muß man von PHP treffen lassen. Hierzu gibt es den Modifier e , der PHP das zweite Argument von [895]preg_replace() als PHP-Code auswerten läßt. Mit folgender Konstruktion kann man in $t alle Vorkommen von $s außerhalb von < und > durch $r ersetzen; die zweite Version ist besonders mit dem Modifier i interessant, um Wörter unabhängig von ihrer Groß-/Kleinschreibung unter Beibehaltung der Schreibweise hervorzuheben: _________________________________________________________________ // $s in $t durch $r ersetzen: preg_replace("/((<[^>]*)|$s)/e", '"\2"=="\1"? "\1":"$r"', $t); // $s case-insensitive in $t hervorheben: preg_replace("/((<[^>]*)|$s)/ie", '"\2"=="\1"? "\1":"\1"', $t); _________________________________________________________________ 6.12. Wie mache ich aus URIs im Text anklickbare Links? | [896]Hyperlink | [897]HTML | Antwort von [898]Björn Schotte Besten Dank an [899]Thomas Weinert , von dem die ursprüngliche RegExp stammt. Folgender regulärer Ausdruck ersetzt alle normalen URIs, das heißt zum Beispiel http://www.phpcenter.de/, news:de.comp.lang.php, mailto:bjoern@thinkphp.de oder ftp://ftp.suse.com/ durch HTML-Code, damit diese URIs für den Benutzer klickbar werden. _________________________________________________________________ /** * replace URIs with appropriate HTML code to be clickable. */ function replace_uri($str) { $pattern = '#(^|[^\"=]{1})(http://|ftp://|mailto:|news:)([^\s<>]+)([\s\n<>]|$ )#sm'; return preg_replace($pattern,"\\1\\2\\3\\4",$st r); } _________________________________________________________________ 7. Arrays und Arrayvariablen 7.1. [900]Wie kann ich ein Element an ein Array anfügen? 7.2. [901]Wie kann ich ein Array aufzählen? 7.3. [902]Wie kann ich ein Element aus einem Array löschen? 7.4. [903]Wie greife ich auf ein mehrdimensionales Array zu? 7.5. [904]Wie kann ich ein Array nach einem beliebigen Kriterium sortieren lassen? 7.6. [905]Wie kann ich Duplikate aus einem Array entfernen? 7.7. [906]Wie kann ich ein Array von einer Seite auf eine andere transportieren? 7.1. Wie kann ich ein Element an ein Array anfügen? | [907]erzeugen | [908]einfuegen | [909]Element | Antwort von [910]Kristian Köhntopp Durch Verwendung des leeren Arrayoperators wird an ein Array ein Element angehängt. In Code: _________________________________________________________________ $avar[] = "neues element"; _________________________________________________________________ In PHP 4 kann man mit der Funktion [911]array_push() auch mehrere Elemente gleichzeitig an ein Array anfügen. Durch Verwendung eines Index kann man ein Element an einer bestimmten Stelle in einem Array ansprechen. Der Index kann numerisch oder ein String sein: _________________________________________________________________ $avar[1] = "Element mit dem Index 1"; $avar["bla"] = "Element mit dem Index 'bla'"; _________________________________________________________________ 7.2. Wie kann ich ein Array aufzählen? | [912]aufzaehlen | [913]enumeration | [914]Iterator | [915]Maechtigkeit | Antwort von [916]Kristian Köhntopp Ein Array enthält $anz = count($avar) viele Elemente. Man kann diese Elemente mit einer for -Schleife aufzählen, falls die Indices numerisch-zusammenhängend sind: _________________________________________________________________ $anz = count($avar); for ($i=0; $i<$anz; $i++): printf("i: %d avar[%d] = %s
\n", $i, $i, $avar[$i]); endfor; _________________________________________________________________ Für assoziative Arrays ist dieses Konstrukt besser geeignet: _________________________________________________________________ if (isset($avar) && is_array($avar)): reset($avar); while(list($k, $v) = each($avar)): printf("k=%s v=%s
\n", $k, $v); endwhile; endif; _________________________________________________________________ Es macht Gebrauch von den Funktionen [917]reset() um den internen Positionszeiger eines Arrays zurückzusetzen, [918]list() um einen Zuweisungskontext für ein Wertepaar $k und $v zu erzeugen und [919]each() um den Schlüssel ( key , k ) und den Wert ( value , v ) an der aktuellen Position des Arrays auszulesen. Von der Anwendung der Funktionen [920]next() , [921]prev() und [922]current() ist in diesem Zusammenhang abzuraten, da sie bei Arrays mit Nullwerten falsche Ergebnisse liefern. Diese Schleife wird nur die Werte -2 und -1 drucken, da hier der Wert 0 nicht vom Feldende unterschieden werden kann: _________________________________________________________________ $avar = array(-2, -1, 0, 1, 2); for (reset($avar); $v = current($avar); next($avar)): printf("v = %d
\n", $v); endfor; _________________________________________________________________ Ab PHP4 gibt es die Funktion [923]foreach() , mit der man auf einfache Weise ein Array durchlaufen kann. 7.3. Wie kann ich ein Element aus einem Array löschen? | [924]Array | [925]loeschen | [926]Element | [927]entfernen | Antwort von [928]Johannes Frömter Mit [929]unset() kann man sowohl Variablen (Strings, ganze Arrays etc.) als auch einzelne Elemente eines Arrays löschen: _________________________________________________________________ $array = array('P', 'H', 'P'); // Löscht das 'H' unset($array[1]); // Löscht das ganze Array unset($array); _________________________________________________________________ Durch das Löschen einzelner Einträge entstehen Lücken im Index des Arrays (d.h. $array[1] greift ins Leere); die Array-Funktionen selbst (wie z.B. [930]foreach() oder [931]next() ) stört dies jedoch nicht. Benötigt man dennoch ein Array mit fortlaufendem Index für direkten Zugriff, muß man es aus den verbleibenden Elementen neu erstellen: _________________________________________________________________ // Array mit fortlaufendem Index erzeugen $array = array_values($array); _________________________________________________________________ In PHP4 kann man auch mit [932]array_splice() Teile eines Arrays entfernen, und dabei automatisch einen zusammenhängenden numerischen Index erzeugen lassen (falls das benötigt wird): _________________________________________________________________ // "In $array ab Index 1 ein Element durch nichts ersetzen" array_splice($array, 1, 1); _________________________________________________________________ 7.4. Wie greife ich auf ein mehrdimensionales Array zu? | [933]Dimension | Antwort von [934]Kristian Köhntopp Das läßt sich am einfachsten an einem kleinen Beispiel zeigen: _________________________________________________________________ $array = array( "foo" => array ("a", "b"), "bar" => array ("c", "d") ); print($array["foo"][0]); // gibt "a" aus. print($array["bar"][1]); // gibt "d" aus. _________________________________________________________________ 7.5. Wie kann ich ein Array nach einem beliebigen Kriterium sortieren lassen? | [935]Sortierkriterium | [936]aufsteigend | [937]absteigend | Antwort von [938]Kristian Köhntopp PHP stellt eine Reihe von vordefinierten Sortierfunktionen zur Verfügung. Wenn diese nicht ausreichen, kann man mit Hilfe der Funktion [939]usort() nach beliebigen Kriterien sortieren lassen. Der Funktion muß eine Vergleichsfunktion und das zu sortierende Array als Parameter mit übergeben werden. Das nachfolgende Beispiel sortiert ein Array von Paaren alphabetisch nach dem 2. Element. _________________________________________________________________ kris@valiant:~ > ./php $b[1])?1:-1; } usort ($a, "cmp"); reset($a); while(list($k, $v) = each($a)) printf("k = %s v[0] = %s v[1] = %s\n", $k, $v[0], $v[1]); ?> X-Powered-By: PHP/4.0b5-dev Content-type: text/html; charset=iso-8859-1 type a = array type b = array k = 0 v[0] = 2 v[1] = Albert k = 1 v[0] = 0 v[1] = Schmidt kris@valiant:~ > _________________________________________________________________ Antwort von [940]Johannes Frömter Ab PHP4 gibt es die Funktion [941]array_multisort() , die - gefüttert mit einem passenden (eindimensionalen) "Sortierarray" - mehrdimensionale Arrays nach beliebigen Dimensionen ordnen kann. _________________________________________________________________ foreach($a as $v) $s[] = $v[1]; // [1] = Dimension zur Sortierung array_multisort($s, SORT_ASC, $a); // ASC = auf-, DESC = absteigend _________________________________________________________________ Durch Einfügen weiterer "Sortierarrays" ( $s ) vor dem "Nutzarray" $a kann man auch nach mehreren Kriterien gleichzeitig sortieren lassen. Dabei kann nach jedem "Sortierarray" die Sortierrichtung und -art neu bestimmt werden. 7.6. Wie kann ich Duplikate aus einem Array entfernen? | [942]Mengen | Antwort von [943]Johannes Frömter Ab PHP Version 4.0.1 gibt es die Funktion [944]array_unique() , um doppelte Einträge in Arrays zu eliminieren. Anmerkung: array_unique() behält die Indizes des Original- Arrays bei; um auf die Elemente des Arrays mit fortlaufenden numerischen Indizes von 0 bis count()-1 zugreifen zu können, verwende man _________________________________________________________________ $a = array_values(array_unique($a)); _________________________________________________________________ In PHP3 kann man sich z.B. mit folgender Konstruktion behelfen: _________________________________________________________________ function array_unique($a) { while (list($k, $v) = each($a)) $b[$v] = ""; while (list($k, $v) = each($b)) $c[] = $k; return $c; } _________________________________________________________________ 7.7. Wie kann ich ein Array von einer Seite auf eine andere transportieren? | [945]Session | Antwort von [946]Martin Jansen Die einzig vernünftige und sichere Methode, um ein Array von einer Seite auf eine andere Seite zu transportieren, ist das Ablegen des Arrays in einer Session auf dem Server. Alle anderen Methoden stellen ein Sicherheitsrisiko dar und sollten nicht angewendet werden. Weitere Informationen zu Sessions finden sich in [947]PHP4: Sessions und in [948]Was sind Sessions und warum sind sie nützlich? Im Prinzip kann man ein Array mit [949]serialize() in einen String verwandeln und per URL weiterreichen (URL-Codierung etc. nicht vergessen), aber das ist wegen der begrenzten Länge von URLs und der Manipulationsmöglichkeit durch den Benutzer zu vermeiden. 8. Klassen und Objekte 8.1. [950]Warum Klassen und Objekte benutzen? 8.2. [951]Wie definiere ich eine Klasse? Wie erzeuge ich ein Objekt? 8.3. [952]Was ist $this? 8.4. [953]Was ist extends? Was ist Vererbung? 8.5. [954]Was ist ein Konstruktor? 8.6. [955]Was sind polymorphe Funktionen? Kann ich sie simulieren? 8.7. [956]Wie kann ich Metainformationen über eine Klasse bekommen? 8.8. [957]Wie speichere ich ein Objekt in einer Session? 8.1. Warum Klassen und Objekte benutzen? | [958]Klasse | [959]Objekt | [960]Kapselung | [961]Namensraum | [962]Funktion | [963]Wiederverwendbarkeit | [964]Grundlagen | [965]Warum | [966]Zweck | [967]Methode | [968]Eigenschaft | Antwort von [969]Kristian Köhntopp Der Artikel [970]Data Driven Websites mit PHP, Teil 2 zeigt, daß es recht schwierig ist, Code zu bauen, der sowohl komfortabel als auch wiederverwendbar ist. Mit Hilfe von Klassen läßt sich solcher Code so kapseln, daß er vergleichsweise störungsfrei in existierende Projekte eingesetzt werden kann, ohne Gefahr zu laufen, mit bereits benutzten Funktions- und Variablennamen zu kollidieren. 8.2. Wie definiere ich eine Klasse? Wie erzeuge ich ein Objekt? | [971]Klasse | [972]Objekt | [973]erzeugen | [974]definieren | [975]ableiten | [976]Beispiel | Antwort von [977]Kristian Köhntopp Angenommen, es ist eine Reihe von Funktionen vorhanden, die mit einer Datenbank kommunizieren und diese Funktionen sollen in eine Klasse umgewandelt werden: _________________________________________________________________ $Link_ID = 0; // ID der aktuellen DB-Verbindung $Query_ID = 0; // ID des aktuellen Abfrageresultates $Error = 0; // Letzte Datenbank-Fehlermeldung function connect() { ... } function query() { ... } function next_record() { ... } function num_rows() { ... } _________________________________________________________________ Aus diesen Variablen und Funktionen wird eine Klasse, indem man vor alle verwendeten Variablen das Schlüsselwort var schreibt und indem man alle Variablen und Funktionen mit einem class -Konstrukt umschließt. _________________________________________________________________ class DB_MiniSQL { var $Link_ID = 0; // ID der aktuellen DB-Verbindung var $Query_ID = 0; // ID des aktuellen Abfrageresultates var $Error = 0; // Letzte Datenbank-Fehlermeldung function connect() { ... } function query() { ... } function next_record() { ... } function num_rows() { ... } } _________________________________________________________________ Klassen selbst sind nur Baupläne, sie erzeugen keine Variablen und die Funktionen, die in ihnen enthalten sind, lassen sich so nicht verwenden. Mit Hilfe der Anweisung new läßt man den PHP-Interpreter eine Variable, ein Objekt, nach diesem Bauplan bauen. _________________________________________________________________ $db1 = new DB_MiniSQL; // $db1 ist ein Objekt der Klasse DB_MiniSQL $db2 = new DB_MiniSQL; // $db2 ist noch ein Objekt derselben Klasse _________________________________________________________________ Das Objekt $db1 kann man sich wie ein Array mit einer besonderen Syntax vorstellen. Anstatt auf $db1["Link_ID"] und $db1["Error"] zuzugreifen, muß man $db1->Link_ID und $db1->Error verwenden. Auch die Funktionen in einem Objekt lassen sich so aufrufen: $db1->connect() , $db1->query() und so weiter. Ein beliebter Fehler ist, $db1->Error zu meinen, aber $db1-> $ Error zu schreiben. Das ist falsch: Der vollständige Name der Variablen ist db1->Error , mit einem $ davor, um ihn als Variablennamen zu kennzeichnen. 8.3. Was ist $this? | [978]Klasse | [979]Objekt | [980]this | [981]Name | [982]Funktion | Antwort von [983]Kristian Köhntopp Innerhalb einer Funktion wie connect() muß auf die Variable Link_ID zugegriffen werden, um das Resultat eines Connect abzuspeichern. In connect() können wir nicht wissen, wie die Funktion nun gerade heißt, also ob ihr Name nun gerade $db1->connect() oder $db2->connect() ist und ob die Link-ID nun in $db1->Link_ID oder in $db2->Link_ID abgespeichert werden muß. Eigentlich ist das auch egal: Wir wollen ja nur auf unsere eigene Link-ID zugreifen. $this bezeichnet nun genau unser eigenes Objekt, also $db1 innerhalb von $db1 und $db2 innerhalb von $db2 . Man schreibt daher code wie _________________________________________________________________ class DB_MiniSQL { var $Link_ID = 0; function connect() { $this->Link_ID = mysql_connect(...); ... } ... } _________________________________________________________________ oder _________________________________________________________________ class DB_MiniSQL { var $Link_ID = 0; function query($query) { // Wenn kein Datenbank-Link vorhanden ist, eines herstellen. if (!$this->Link_ID) $this->connect(); ... } ... } _________________________________________________________________ 8.4. Was ist extends? Was ist Vererbung? | [984]Klasse | [985]Objekt | [986]extends | [987]Vererbung | [988]erweitern | [989]ableiten | Antwort von [990]Kristian Köhntopp Häufig braucht man eine Klasse, die sich genauso verhält wie eine Klasse, die man schon hat, aber mit ganz kleinen Änderungen. Mit Hilfe des Schlüsselworts extends kann man sich eine Klasse definieren, die genauso ist wie eine bereits existierende Klasse und braucht dann nur noch das zu notieren, was anders ist. Die Änderungen können dabei eine bestehende Klasse erweitern, also neue Variablen und Funktionen zu einer Klasse hinzufügen oder bestehende Variablen und Funktionen einer Klasse ersetzen. Der folgende Beispiel-Code definiert eine Klasse Example_SQL , die sich ganz genauso verhält wie die Klasse [991]DB_Sql in [992]PHPLIB . Die Variablen $Host , $User , $Password und $Database sind jedoch anders belegt als in der originalen Klasse: Wir setzen dort einfach die Informationen ein, die notwendig sind, um unsere Datenbank zu kontaktieren. Außerdem ist die Funktion haltmsg() ersetzt. Die Klasse ruft diese Funktion auf, wenn ein Fehler aufgetreten ist. Wir ersetzen diese Funktion durch eine eigene Version, sodaß wir Fehlermeldungen mit den Informationen drucken können, die der Anwender benötigt. _________________________________________________________________ class Example_Sql extends DB_Sql { var $Host = "database.netuse.de"; var $User = "kris"; var $Password = "xyzzy"; var $Database = "example_database"; function haltmsg($msg) { ?> Es ist ein Datenbankfehler aufgetreten. Die Bearbeitung Ihrer Eingaben wurde abgebrochen. Bitte informieren Sie den Webmaster von diesem Problem.

query(...) , $db->next_record() und so weiter aufrufen, als ob man es mit einer Klasse DB_SQL zu tun hätte. Die Klasse zeigt nur in folgenden Punkten abweichendes Verhalten: Sie druckt ihre Fehlermeldungen in deutsch und enthält anwendungsspezifische Kontaktinformationen und sie kontaktiert anders als die Originalklasse defaultmäßig die Datenbank example_database auf dem Host database.netuse.de mit dem angegebenen Usernamen und Paßwort. 8.5. Was ist ein Konstruktor? | [993]Klasse | [994]Objekt | [995]Konstruktor | [996]Funktion | [997]Initialisierung | [998]automatisch | [999]new | [1000]Name | [1001]Destruktor | Antwort von [1002]Kristian Köhntopp Ein Konstruktur ist eine gewöhnliche Funktion einer Klasse. Sie unterscheidet sich von anderen Funktionen derselben Klasse dadurch, daß sie beim Erzeugen der Klasse mit new automatisch aufgerufen wird. In PHP muß ein Konstruktor unglücklicherweise genauso heißen wie die Klasse selbst. Ein Konstruktor kann optionale Parameter mitgegeben bekommen. Er kann niemals ein Funktionsergebnis liefern. Man verwendet Konstruktoren oft, um die Variablen eines Objektes zu initialisieren. Die Klasse Menu in [1003]PHPLIB verwendet beispielweise einen Konstruktor, um eine Menüstruktur zu initialisieren. _________________________________________________________________ class Menu { function Menu() { $this->setup(); } function setup() { reset($this->urlmap); while(...) { ...; } } } _________________________________________________________________ Die Entscheidung, wie in C++ Konstruktoren genauso zu benennen wie die Klasse ist überaus unglücklich, denn auf diese Weise muß man wissen, ob eine Klasse einen Konstruktor hat, wenn man eine Klasse erweitert. _________________________________________________________________ class My_Menu extends Menu { var $urlmap = array( // meine eigenen Menüpunkte hier ); } _________________________________________________________________ Diese abgeleitete Klasse wird nicht funktionieren, denn ihr fehlt der Konstruktor My_Menu() . Die Funktion setup() wird niemals aufgerufen und daher kann das Menü nicht funktionieren. Man muß stattdessen Code wie diesen schreiben: _________________________________________________________________ class My_Menu extends Menu { var $urlmap = array( // meine eigenen Menüpunkte hier ); function My_Menu() { $this->setup(); } } _________________________________________________________________ 8.6. Was sind polymorphe Funktionen? Kann ich sie simulieren? | [1004]Klasse | [1005]Objekt | [1006]Polymorphie | [1007]Signatur | [1008]Returntyp | [1009]Parametertyp | [1010]Parameter | [1011]Parameteranzahl | [1012]Funktion | Antwort von [1013]Kristian Köhntopp Unter Polymorphie versteht man das Verhalten von objektorientierten Sprachen, die Signatur einer Funktion als Bestandteil des Funktionsnamens bei einem Aufruf zu betrachten. Die Signatur einer Funktion sind der Returntyp und die Parametertypen einer Funktion. In einer Sprache mit Polymorphie würde Code wie der folgende funktionieren: _________________________________________________________________ # Funktion f mit Integer-Resultat und Integer-Parametern function int f(int $a, int $b) { return $a*$b; } # Funktion f mit Array-Resultat und Array-Parametern function array f(array $a, array $b) { $r = array(), $l = count($a); for ($i=0; $i<$l; $i++) $r[] = $a[$i] * $b[$i]; return $r; } # Definition von Integer-Parametern $xi = 3; $yi = 4; # Aufruf der Integer-Funktion f. $zi = f($xi, $yi); # Definition von Array-Parametern $xa = array(2, 3, 4); $ya = array(4, 3, 2); # Aufruf der Array-Funktion f. $za = f($xa, $ya); _________________________________________________________________ Dieser Code definiert zwei verschiedene Funktionen, die intern als int_f_int_int() und array_f_array_array() bezeichnet werden können, die im Code aber beide ununterscheidbar f() heißen. Er ruft dann die Funktion f() einmal mit Integer-Parametern und Array-Paramerern auf. Die Sprache ist aufgrund der Polymorphie in der Lage, diese beiden f() zu unterscheiden und korrekt die Funktion int_f_int_int() oder array_f_array_array() aufzurufen. PHP unterstützt keine Polymorphie und kann dies schon deswegen nicht tun, weil die Return- und Parametertypen einer Funktion nicht deklariert werden müssen. Stattdessen muß man manuell mit den Typfunktionen wie folgt codieren: _________________________________________________________________ function f($a, $b) { # Wandle $a in ein Array um, wenn es das nicht ist. if (!is_array($a)) $a = array($a); # Ebenso $b. if (!is_array($b)) $b = array($b); # Normaler Code. $r = array(), $l = count($a); for ($i=0; $i<$l; $i++) $r[] = $a[$i] * $b[$i]; if (count($r) == 1) # Skalar zurückgeben return $r[0]; else # Array zurückgeben return $r; } _________________________________________________________________ 8.7. Wie kann ich Metainformationen über eine Klasse bekommen? | [1014]Klasse | [1015]Objekt | [1016]Metainformationen | [1017]Name | [1018]Returntyp | [1019]Parametertyp | Antwort von [1020]Kristian Köhntopp Metainformationen über eine Klasse oder ein Objekt sind alle Informationen, die man über diese Klasse oder eine Instanz dieser Klasse (ein Objekt) bekommen kann. Sie umfassen den Namen der Klasse eines Objektes und die Namen aller Oberklassen dieser Klasse, die Namen und Typen aller Instanzvariablen des Objektes und die Namen, Returntypen sowie Parametertypen aller Funktionen eines Objektes. In PHP kann man die Klasse eines unbekannten Objektes nicht bestimmen, d.h. man kann keine Funktion _________________________________________________________________ function show_classname($o) { if (is_object($o)) printf("Die Klasse des Objektes ist %s\n", wunderfunktion($o)); } } _________________________________________________________________ schreiben, weil es die benötigte Wunderfunktion nicht gibt. Man kann jedoch die Namen aller Slots (Instanzvariablen und Funktionen) einer Klasse aufzählen und ihre Typen eingeschränkt bestimmen, weil Objekte in PHP3 nur Hashes mit Zuckerguß sind. _________________________________________________________________ kris@valiant:~ > ~/bin/php -q classname den Name der Klasse eines Objektes abfragen und entsprechend kann PHPLIB Objekte korrekt serialisieren. In Zend kann man folgende Metadaten über eine Klasse bestimmen: * get_class zur Bestimmung der Klasse eines Objektes. * get_parent_class zur Bestimmung der Oberklassen eines Objektes. * method_exists zur Bestimmung des Vorhandenseins einer Methode in einem Objekt. * class_exists zur Bestimmung des Vorhandenseins einer Klassendefinition. * is_subclass_of zur Feststellung, ob eine gegebene Klasse eine Unterklasse einer anderen Klasse ist. Da PHP4 vom Zend-Interpreter Gebrauch macht, existieren diese Funktionen auch in PHP4. 8.8. Wie speichere ich ein Objekt in einer Session? | [1023]Klasse | [1024]Objekt | [1025]Session | [1026]speichern | [1027]serialisieren | [1028]Eigenschaften | Antwort von [1029]Johannes Frömter Indem man den Namen des Objektes in der Session registriert. Dabei werden allerdings nur die Eigenschaften des Objektes gespeichert, d.h. die Klassendefinition (das class MyClass {...} -Konstrukt) muß in jedem Fall im Script vorhanden sein. Wird die Klassendefinition per [1030]require() & Co. in das Script eingebunden, muß dies zwingend vor [1031]session_start() geschehen. Wird die Klasse dagegen 'inline', d.h. in derselben physikalischen Datei definiert, spielt die Stelle keine Rolle. _________________________________________________________________ // Methode 1: Instanzierung vor session_start() $obj = new MyClass; session_start(); session_register("obj"); // Methode 2: Nach session_start() und nur im Bedarfsfall instanzieren session_start(); session_register("obj"); if (!is_object($obj)) $obj = new MyClass; _________________________________________________________________ 9. Variablen und Formulare 9.1. [1032]Wie übergebe ich Variablen aus einem Formular an ein PHP-Script? 9.2. [1033]Wie kann ich ohne Formular Variablen an ein Script übergeben? 9.3. [1034]Wie viele Formularelemente kann ich auf einer Seite haben? 9.4. [1035]Sollte ich besser GET oder POST verwenden? 9.5. [1036]Wie verarbeite ich ein -Feld? 9.6. [1037]Wie verarbeite ich eine Textarea? 9.7. [1038]Wie kann ich aus einer Datenbanktabelle einen verarbeiten? 9.9. [1040]Wie kann man Radio-Buttons verarbeiten? 9.10. [1041]Wie kann man Checkboxen verarbeiten? 9.11. [1042]Wie funktioniert ein Datei-Upload über HTML-Formulare? 9.12. [1043]Wie kann ich mehrere Dateien auf einmal uploaden? 9.13. [1044]Wie verarbeite ich ? 9.14. [1045]Wie erkenne ich den Klick auf einen Submit-Button? 9.15. [1046]Wie verarbeite ich mehrere Submit-Buttons? 9.16. [1047]Wie verarbeite ich einen Reset-Button? 9.17. [1048]Wie erkenne ich fehlerhafte/fehlende Eingaben? 9.18. [1049]Wie verhindere ich mehrfaches Absenden eines Formulars? 9.1. Wie übergebe ich Variablen aus einem Formular an ein PHP-Script? | [1050]Formular | [1051]Uebergabe | [1052]Variable | Antwort von [1053]Kristian Köhntopp Gar nicht. Wenn das action= -Attribut eines Formulares ein PHP-Script ist, dann stehen die Variablen aus dem Formular und aus den Cookies automatisch als Elemente in einem von drei Arrays in PHP zur Verfügung: Je nach der Art der Übergabe stehen sie in $HTTP_GET_VARS , $HTTP_POST_VARS oder $HTTP_COOKIE_VARS bereit. Wenn der Schalter register_globals in der Konfiguration von PHP4 gesetzt ist (und in PHP3 immer), dann stehen sie außerdem als globale Variablen bereit. Hinweis: Dies ist ein Sicherheitsrisiko und nicht empfohlen. Weil es sich bei diesen drei Arrays um globale Variablen handelt, sind sie in Funktionen nicht sichtbar, es sei denn, man übergibt sie als Parameter oder importiert sie mit Hilfe der Anweisung global . Die Stellung des Schalters register_globals kann man in der Ausgabe von [1054]phpinfo() leicht prüfen. 9.2. Wie kann ich ohne Formular Variablen an ein Script übergeben? | [1055]Formular | [1056]Variable | [1057]Uebergabe | [1058]URL | [1059]GET | [1060]Script | [1061]weitergeben | Antwort von [1062]Kristian Köhntopp Wenn GET-Variablen Zeichen enthalten bzw. zur Laufzeit enthalten können, die nicht im Klartext in URLs auftauchen dürfen (Umlaute, Leerzeichen, Prozentzeichen etc.), muß man die Variablen mit [1063]urlencode() codieren, bevor man sie an die URL anhängt. Um die Decodierung muß man sich im Normalfall nicht kümmern, das geschieht automatisch. Mit folgendem Script lassen sich mehrere Werte - übergeben als array(Variable => Wert) - bequem codieren: _________________________________________________________________ "b", "c" => "d" ); $url = req_url("beispiel.php", $p); ?> Klicke auf das Beispiel. _________________________________________________________________ 9.3. Wie viele Formularelemente kann ich auf einer Seite haben? | [1064]Anzahl | [1065]Formular | [1066]Elemente | [1067]Limit | [1068]URL | [1069]GET | [1070]POST | Antwort von [1071]Kristian Köhntopp Wird das Formular mit POST übergeben, ist die Anzahl und Größe der Elemente möglicherweise begrenzt durch serverseitige Einstellungen (Apache: siehe LimitRequestBody und verwandte Direktiven). Wird das Formular mit GET übergeben, ist die Anzahl der Variablen begrenzt durch die maximale Länge der URL, die der Browser und der Webserver verarbeiten können. Beim Browser ist dies vom Browser und der Browserversion abhängig. Beim Webserver ist das Limit unter Umständen konfigurierbar (Apache: siehe LimitRequestLine (8190) und verwandte Direktiven). 9.4. Sollte ich besser GET oder POST verwenden? | [1072]GET | [1073]POST | [1074]Formular | [1075]Methode | [1076]URL | Antwort von [1077]Kristian Köhntopp Im allgemeinen ist es besser, die Methode GET zu verwenden: Formulare sind leichter zu debuggen und der Anwender kann sich ein fertig ausgefülltes Formular mit Parametern in die Bookmarks oder einen Link legen - das ist bequem und ergonomisch. Enthält das Formular Werte, die nicht in der URL angezeigt werden sollen und die ggf. nicht Bestandteil des Referer sein sollen und nicht in Proxy-Logs auftauchen sollen, dann ist die Verwendung von POST angezeigt. Dies ist zum Beispiel immer der Fall, wenn ein Eingabeelement Password verwendet wird. Ebenfalls soll POST verwendet werden, wenn die Länge von Eingabeelementen nicht nach oben begrenzt ist, also immer dann, wenn ein TEXTAREA verwendet wird. Schließlich ist die Verwendung von POST zwingend notwendig, wenn ein File-Upload durchgeführt werden soll, einmal wegen der prinzipiell unbegrenzten Länge, aber auch weil der notwendige ENCTYPE="multipart/form-data" nur mit POST zusammen funktioniert. 9.5. Wie verarbeite ich ein -Feld? | [1078]Formular | [1079]Text | [1080]Input | [1081]HTML | Antwort von [1082]Johannes Frömter Normale Input-Felder eignen sich für einzeilige Eingaben von 1 bis ca. 100 Zeichen. In HTML werden sie als definiert, wobei der Inhalt von name in PHP zum Namen der Variable wird, die die Eingabe des Benutzers enthält: $HTTP_GET_VARS['variable'] bzw. $HTTP_POST_VARS['variable'] (ab PHP4.1 $_GET['variable'] bzw. $_POST['variable'] ), je nach Methode. Für eine Vorbelegung des Feldes gibt es das optionale Attribut value : _________________________________________________________________ _________________________________________________________________ Da die Variable auch Anführungszeichen enthalten könnte (was das Ende des value-Feldes bedeuten würde), muß man sie durch [1083]htmlspecialchars() "entschärfen" lassen. Formular-Felder werden von PHP immer als Variablen vom Typ string zur Verfügung gestellt - auch wenn das Feld "nichts", oder wenn es nur Zahlen enthält. Siehe hierzu auch: " [1084]Wie erkenne ich fehlerhafte/fehlende Eingaben? ". 9.6. Wie verarbeite ich eine Textarea? | [1085]Formular | [1086]Text | [1087]Input | [1088]Textarea | [1089]mehrzeilig | [1090]HTML | Antwort von [1091]Johannes Frömter 9.7. Wie kann ich aus einer Datenbanktabelle einen _________________________________________________________________ Die Funktion lovselection() generiert aus einer Tabelle von Werten in der Datenbank (eine sogenannte List Of Values, LOV) eine Reihe von Option-Tags. Man kann dann leicht den passenden Select-Container drumwickeln. lovselection() verwendet die Datenbank-Klasse von PHPLIB, läuft also mit prinzipiell jeder SQL-Datenbank. Wenn man einen solchen Select-Tag generiert, dann heißt das natürlich nicht, daß das so erzeugte Formular on Submit ausschließlich Werte zurückliefert, die man dort zur Auswahl gestellt hat. Stattdessen kann jeder beliebige Wert zurück kommen. Es ist also notwendig, dass man ein Prädikat erzeugt, das überprüft, ob der eingegangene Wert gültig ist. Dieses Prädikat ist is_validlov() : Die Funktion liefert true, wenn der gegebene Wert genau einmal in der angegebenen Tabelle vorkommt. Im Beispiel kann mit der Variablen $f_ortsnetz , die aus dem Formular kommt, nicht gearbeitet werden - sie kann potentiell ungültige Werte ("003432") enthalten oder sogar potentiell gefährliche Werte ("0431' or sp_clearall() or '0431", wobei sp_clearall() irgendeine Einbaufunktion oder Stored Procedure in einer Datenbank ist, die gefährliche Dinge mit der Datenbank macht). Der Wert von $f_ortsnetz kann nach $ortsnetz kopiert und gefahrlos verwendet werden genau dann und nur dann, wenn is_validlov() wahr ist, denn dann kommt der Inhalt von $f_ortsnetz genau einmal in der angegebenen Tabelle vor und ist damit eine gültige und garantiert harmlose Auswahl. Die Funktion is_validlov() selbst kann sich den Luxus nicht erlauben, mit harmlosen Werten zu arbeiten und muß daher so geschrieben sein, daß sie auch mit gefährlichen Inhalten in $value zurecht kommt. Ein - noch recht schwacher, aber einigermassen allgemeingültiger - Versuch, dies zu gewährleisten ist die Anwendung von [1101]addslashes() bevor der Wert von $value in das SELECT-Statement eingesetzt wird. Besser wäre es, könnte man Annahmen ueber das Aussehen von $value machen (keine Spaces, keine Single Quotes, keine Backslashes, ...) und würde man dann mit [1102]preg_match() abtesten, ob $value so aussieht, wie man erwartet, bevor man den Wert in das SELECT-Statement einsetzt. 9.8. Wie kann man ein

Hallo

_________________________________________________________________ Das empfangende PHP-Script bekommt das Resultat des Datei-Uploads in einer Reihe von globalen Variablen mit dem Namensprefix $probe übermittelt, weil das Input-Element im Formular diesen Namen hat. $probe Diese Variable enthält den Namen der Datei in einem temporären Verzeichnis auf dem Server. Sie kann von dort mit einem [1134]copy() -Aufruf abgeholt werden. Das ist auch notwendig, da die Originaldatei am Ende des Scriptes automatisch gelöscht wird. $probe_name Diese Variable enthält den Namen der Datei auf dem System des Anwenders. Der genaue Dateiname mit evtl. vorhandenen Laufwerksbuchstaben, Pfadseparatoren und anderen Sonderzeichen ist betriebssystemabhängig und das empfangende Script sollte keine Annahmen hierüber machen. $probe_size Diese Variable enthält die Länge der Datei auf dem Server in Bytes. $probe_type Diese Variable enthält den MIME-Type der Datei, so wie er dem Server vom Browser übermittelt worden ist. Der Upload von Dateien wird durch die beiden Konfigurationsparameter upload_tmp_dir und upload_max_filesize in der php3.ini gesteuert. Und in Windows muß man sich in der php3.ini dringend ein gültiges upload_tmp_dir definieren, bevor das gezeigte Beispiel funktionieren kann. Des weiteren ist es unter Umständen notwendig, die Variable $probe mit der PHP-Funktion [1135]stripslashes() zu bearbeiten, bevor man auf sie mit [1136]copy() oder einem anderen Befehl zugreift. Dies liegt daran, dass Windows Pfadangaben scheinbar quotet, was der Befehl [1137]stripslashes() wieder rückgängig macht. Der Pfad zu upload_tmp_dir muß absolut angegeben werden. PHP legt die temporäre Datei in dem angegebenen Verzeichnis an und löscht sie am Ende des Scriptes wieder. Die Datei darf maximal die angegebene Größe haben. Ein Einstellen der Größenbegrenzung begrenzt jedoch nicht wirklich den Plattenplatz, der auf dem Server von PHP durch Fileupload verbraucht wird: Aus technischen Gründen muß PHP die Datei zunächst empfangen und kann sie erst dann verwerfen, wenn sie zu groß ist. Seit PHP 3.0.10 kann mehr als eine Datei pro Formular hochgeladen werden. Achtung: Aufgrund eines [1138]Bugs in PHP muß das Script nach dem Upload prüfen, ob sich der/die Name(n) des Datei-Formularfeldes (im folgenden Beispiel probe ) in den Hashes $HTTP_GET_VARS , $HTTP_POST_VARS oder $HTTP_COOKIE_VARS befindet. Ist dem so, muß das Script die Weiterverarbeitung (Kopieren) der - angeblich - hochgeladenen Datei(en) verweigern. Mehr über neue Funktionen zum Upload-Handling der PHP Versionen > 3.0.16 und > 4.0.2 gibt es im Kapitel [1139]POST method uploads auf [1140]www.php.net . Vollständiges Beispiel: _________________________________________________________________

Upload


\n", $probe_name); printf("Sie ist %s Bytes groß und vom Typ %s.
\n", $probe_size, $probe_type); endif; ?> _________________________________________________________________ 9.12. Wie kann ich mehrere Dateien auf einmal uploaden? | [1141]Datei | [1142]Upload | [1143]input | [1144]POST | [1145]mehrfach | [1146]enctype | [1147]HTML | [1148]Verzeichnis | Antwort von [1149]Johannes Frömter Das Auswählen mehrerer Dateien oder gar ganzer Verzeichnisse ist mit einem -Feld nicht möglich. Auch das Vorgeben eines bestimmten Verzeichnisses oder vollständigen Pfades ist bei File-Input-Feldern unterbunden, sei es als Angabe value="path/to/file" oder per JavaScript. Warum? Aus Sicherheitsgründen! Wem wäre es schon recht, wenn auf einer x-beliebigen Internetseite sich ein (z.B. durch Layer verstecktes) Formular mit einem Feld mittels JavaScript selbsttätig abschicken würde? Eben deshalb muß jede Datei, die verschickt werden soll, vom Anwender manuell und damit bewußt ausgewählt werden. Mehrere Dateien lassen sich verschicken * mit mehreren -Feldern - pro Datei eines (Tipp: [] an den Namen des Input-Feldes anhängen, um in PHP ein Array mit den Dateiinformationen zu erhalten) * als .zip - oder .tar -Datei * per FTP * mit einem eigenen Tool, das auf dem Rechner des Absenders installiert werden muß * per (Java-) Applet - hier gibt es mehrere kommerzielle Lösungen, die allerdings durchweg mit 200-400 US-Dollar zu Buche schlagen, z.B. + [1150]AppletFile + [1151]JFile + [1152]JUpload 9.13. Wie verarbeite ich ? | [1153]Formular | [1154]Submit | [1155]Image | [1156]Koordinaten | Antwort von [1157]Kristian Köhntopp In Formularen kann man statt eines SUBMIT auch ein IMAGE als Absendeknopf installieren. Dies sieht dann so aus: _________________________________________________________________ _________________________________________________________________ Wenn der User das Bild anklickt, werden zwei Variablen mit den Namen sub.x und sub.y erzeugt, die die Koordinaten des Klicks relativ zur linken, oberen Ecke des Bildes beschreiben. Da Variablennamen in PHP keine Punkte enthalten dürfen, wandelt PHP die Punkte in Unterstriche um. Im Beispiel bekommt man die Variablen mit den Namen $sub_x und sub_y übergeben. Antwort von [1158]Johannes Frömter Alternativ kann man an den Variablennamen eines eckige Klammern [] anhängen; man erhält in PHP dann ein Array mit dem Namen des Buttons, das die Koordinaten des Klickpunktes enthält. Mehrere solcher Image-Buttons kann man als button[a][], button[b][] usw. benennen und die Werte aus den Arrays $button['a'], $button['b'] usw. auslesen. Ob ein bestimmter Button gedrückt wurde, überprüft man mit [1159]isset() : if (isset($button['b'])) . 9.14. Wie erkenne ich den Klick auf einen Submit-Button? | [1160]Submit | [1161]Formular | [1162]Button | [1163]Klick | Antwort von [1164]Johannes Frömter 9.15. Wie verarbeite ich mehrere Submit-Buttons? | [1165]Submit | [1166]Formular | [1167]Button | [1168]mehrere | [1169]verschiedene | [1170]HTML | Antwort von [1171]Johannes Frömter Es gibt verschiedene Möglichkeiten, in PHP zu unterscheiden, welcher Submit-Button in einem HTML-Formular betätigt wurde: * Haben die Buttons den gleichen Namen ( name="submit" ), kann man den value (gleichzeitig Beschriftungstext des Buttons) auswerten; die PHP-Variable heißt so wie der Button ( $submit ), wenn register_globals eingeschaltet ist, ansonsten greift man via $HTTP_POST_VARS['submit'] bzw. $HTTP_GET_VARS['submit'] - je nach Übermittlungsmethode - auf den Wert zu. * Haben die Buttons unterschiedliche Namen, erhält man je nach betätigtem Button eine Variable mit anderem Namen registriert; mit [1172]isset() kann man prüfen, ob eine bestimmte Variable vorhanden ist, d.h. ob ein bestimmter Button angeklickt wurde. * Benennt man die Buttons in der Array-Schreibweise ( name="submit[0]" , zwischen den eckigen Klammern müssen eindeutige Werte stehen), erhält man in PHP ein Array mit genau einem Element; der Schlüssel (Key) dieses Elementes ist der aktivierte Button. Im Script kann man dann z.B. unterschiedliche Anweisungsblöcke mit [1173]include() einbinden und somit ausführen. 9.16. Wie verarbeite ich einen Reset-Button? | [1174]Submit | [1175]Formular | [1176]Button | [1177]Reset | [1178]Zuruecksetzen | [1179]Loeschen | Antwort von [1180]Johannes Frömter 9.17. Wie erkenne ich fehlerhafte/fehlende Eingaben? | [1181]Formular | [1182]Verarbeitung | [1183]Fehler | [1184]leer | [1185]falsch | [1186]Eingabe | Antwort von [1187]Johannes Frömter 9.18. Wie verhindere ich mehrfaches Absenden eines Formulars? | [1188]Formular | [1189]Verarbeitung | [1190]Submit | [1191]mehrfach | [1192]doppelt | Antwort von [1193]Johannes Frömter 10. Dateifunktionen und Programmausführung 10.1. [1194]Wie kann ich eine Datei auslesen? 10.2. [1195]Wie kann ich ein externes Programm von PHP aus starten? 10.3. [1196]Wie realisiere ich einen Dateidownload mit PHP? 10.4. [1197]Wie kann ich in einer Datei eine Zeile einfügen oder löschen? 10.5. [1198]Wie kann ich eine Datei zeilenweise rückwärts auslesen? 10.6. [1199]Wie kann ich einen Datei-Upload per FTP durchführen? 10.7. [1200]Unix: Welche Zugriffsrechte brauche ich, um eine Datei anzulegen? 10.8. [1201]Wie kann ich mit PHP auf die serielle Schnittstelle zugreifen? 10.9. [1202]Warum funktioniert unlink() unter Windows nicht? 10.10. [1203]Wie wende ich include() mit verschachtelten Verzeichnissen an? 10.1. Wie kann ich eine Datei auslesen? | [1204]Datei | [1205]Inhalt | [1206]oeffnen | [1207]Zeile | [1208]lesen | [1209]safe_mode | Antwort von [1210]Kristian Köhntopp Man kann eine Datei manuell Öffnen und Zeile für Zeile lesen: _________________________________________________________________ $fp = @fopen("datei", "r") or die ("Kann Datei nicht lesen."); while ($line = fgets($fp, 1024)): machwas($line); endwhile; fclose($fp); _________________________________________________________________ Dies verwendet die Funktionen [1211]fopen() und [1212]fgets() . Wenn die gelesenen Zeilen sofort ausgegeben werden sollen, dann kann man dies kürzer mit [1213]fpassthru() oder gar [1214]readfile() schreiben: _________________________________________________________________ $fp = @fopen("datei", "r") or die ("Kann Datei nicht lesen."); fpassthru($fp); # fclose($fp); entfällt. # Noch einfacher ist es mit readfile(): readfile("datei"); _________________________________________________________________ Will man stattdessen die Daten in der Datei in einem Array zur Verfügung haben, kann man [1215]file() verwenden. Will man die Daten in der Datei in einem einzigen String zur Verfügung haben, muß man dies mit [1216]implode() kombinieren: _________________________________________________________________ # Einlesen in Array $avar = file("datei"); # Einlesen in String $str = implode("", file("datei")); # Mit unterdrückten Meldungen $str = implode("", @file("datei")); _________________________________________________________________ In jedem Fall kann man den Funktionen wie üblich einen Klammeraffen voranstellen, um die [1217]Fehlermeldungen zu unterdrücken . Die häufigste Fehlermeldung bei fopen() & Co. ist " [1218]Warning: Supplied argument is not a valid File-Handle resource ". Im [1219]safe_mode unterliegt das Lesen und Schreiben von Dateien weiteren Einschränkungen. 10.2. Wie kann ich ein externes Programm von PHP aus starten? | [1220]Programm | [1221]ausfuehren | [1222]extern | Antwort von [1223]Kristian Köhntopp PHP kennt nicht weniger als fünf Mechanismen, um externe Kommandos (z.B. Unix-Shellbefehle) von PHP aus zu starten. Alle diese Mechanismen können zu einem Sicherheitsrisiko werden, wenn man Benutzereingaben Bestandteil der ausgeführten Kommandos oder Dateinamen werden läßt. Durch Anwendung der Funktion [1224]EscapeShellCmd() kann man das Risiko etwas vermindern (etwa: system(escapeshellcmd($cmd)) ). Dennoch empfiehlt es sich, die Parameter, die in die Gestaltung von $cmd eingehen, sorgfältig zu prüfen. Externe Kommandos werden bei Verwendung von CGI PHP unter der Identität des CGI-Wrappers ausgeführt, bei Verwendung einer Modulversion von PHP mit der Identität des Webservers (siehe auch [1225]Webserver verstehen und tunen von Kristian Köhntopp). Will man ein externes Kommando einfach nur ausführen, kann man das betreffende Kommando einfach in Backticks setzen: _________________________________________________________________ `touch yyy`; _________________________________________________________________ Dies wird im selben Verzeichnis wie das PHP-Script die Datei yyy erzeugen, falls der Webserver dort Schreibrecht hat. Alternativ kann man ein Kommando durch die Funktion [1226]exec() starten lassen. Auch hier ist die Ausgabe nicht sichtbar, kann aber in einem Array abgelegt werden: _________________________________________________________________ exec("cat /etc/group", $lines, $result); echo "result = $result
"; echo "Lines
\n"; reset($lines); while(list($k, $v) = each($lines)): echo "k=$k v=$v
\n"; endwhile; _________________________________________________________________ Die Ausgabe des Befehls wird im Feld $lines zur Verfügung gestellt, der Exitcode des Befehls in $result . Die Funktion [1227]system() gibt die Ausgabe des Unix-Kommandos dagegen an den Webserver weiter. Ebenso [1228]passthru() : _________________________________________________________________ system("ls -l", $result); echo "Result: $result
\n"; passthru("ls -l", $result); echo "Result: $result
\n"; _________________________________________________________________ Schließlich kann man externe Kommandos noch mit Hilfe der Funktion [1229]popen() starten: _________________________________________________________________ $fp = popen("ls -l", "r"); while($line = fgets($fp, 1024)): printf("%s
\n", $line); endwhile; _________________________________________________________________ Im [1230]safe_mode unterliegt die Programmausführung weiteren Einschränkungen. Antwort von [1231]Johannes Frömter PHP wartet bei der Ausführung externer Programme auf deren Beendigung, d.h. das PHP-Script ist solange blockiert, bis das aufgerufene Programm fertig ist. Um dies zu vermeiden, muß man den Output des Programms umleiten, z.B. nach /dev/null: _________________________________________________________________ exec("programm >/dev/null 2>&1"); _________________________________________________________________ 10.3. Wie realisiere ich einen Dateidownload mit PHP? | [1232]Datei | [1233]Download | [1234]Header | [1235]MIME-Typ | Antwort von [1236]Kristian Köhntopp Grundsätzlich kann man einen Dateidownload auf zwei verschiedene Arten realisieren: Entweder man schreibt ein PHP-Script, das einen Redirect (siehe ) auf die zu ladende Datei generiert, oder man startet den Download durch das PHP-Script. Die Methode mit dem Redirect hat den Nachteil, daß Anwender die Ziel-URL des Redirect mitbekommen und später dann direkt und ungeschützt auf diese Datei zugreifen können. Will man das verhindern, muß man den Download innerhalb von PHP abhandeln. Die zu ladenden Dateien liegen dann außerhalb der Document Root des Webservers (haben also keine URL) und sind nur durch PHP zugreifbar. In PHP sendet man den passenden MIME-Typ als Header und schickt dann die gewünschte Datei hinterher. Natürlich kann man vorher noch einen Downloadzähler aktualisieren oder überprüfen, ob der Anwender überhaupt für den Download autorisiert ist. _________________________________________________________________ # $download sei der Bezeichner für die zu ladende Datei # Dieses Verzeichnis liegt außerhalb der Document_Root und # ist nicht per URL zuzugreifen. $basedir = "/home/www/download"; # Übersetzung von Download-Bezeichner in Dateinamen. $filelist = array( "file1" => "area1/datei1.zip", "file2" => "area1/datei2.zip", "file3" => "area2/datei1.zip" ); # Einbruchsversuch abfangen. if ($filelist[$download] == "") die("Datei $download nicht vorhanden."); # Vertrauenswürdigen Dateinamen basteln. $filename = sprintf("%s/%s", $basedir, $filelist[$download]); # Passenden Datentyp erzeugen. header("Content-Type: application/octet-stream"); # Passenden Dateinamen im Download-Requester vorgeben, # z.B. den Original-Dateinamen $save_as_name = basename($filelist[$download]); header("Content-Disposition: attachment; filename=\"$save_as_name\""); # Datei ausgeben. readfile($filename); _________________________________________________________________ Dieses Script kann mit dem Parameter $download aufgerufen werden. Dieser Parameter wird dann in den Namen einer zu ladenden Datei übersetzt. Aus Sicherheitsgründen ist es nicht möglich, dem Script direkt Dateinamen zu übergeben - wir möchten vermeiden, daß jemand als Parameter download=../../../../../../../etc/passwd oder ähnliche Namen erfolgreich übergeben kann. Antwort von Guido Haeger Die Reaktion des UserAgent auf die oben genannten Header ist in den RFCs für HTTP und MIME nicht eindeutig definiert. Eventuell versucht der jeweilige User-Agent deshalb, die Datei mit der Standardanwendung für die jeweilige Extension zu öffen. Über Probleme wurde insbesondere bei einigen Versionen des Microsoft Internet Explorers in Verbindung mit PDF-Dateien berichtet. Laut einigen Berichten in der Newsgroup de.comp.lang.php kann die Verwendung von Content-Type-Header wie "x-type/subtype" die Probleme lösen. Das einleitende "x-" kennzeichnet diese Content-Type-Angabe als nicht standardisierten Content-Type. Das Verhalten des jeweiligen User-Agents bei derartigen Headern ist somit ebenfalls nicht standardisiert. 10.4. Wie kann ich in einer Datei eine Zeile einfügen oder löschen? | [1237]Datei | [1238]einfuegen | [1239]loeschen | [1240]aendern | [1241]Anfang | [1242]Ende | [1243]Mitte | [1244]Zeile | [1245]Datenbank | [1246]ueberschreiben | Antwort von [1247]Kristian Köhntopp Für dieses Problem gibt es keine elegante oder effiziente Lösung. Die Ursache liegt darin, wie Unix und Windows die unterliegenden Dateien handhaben, nämlich als unstrukturierte Byteströme. Für diese Byteströme gibt es keine Indices und auch keine Methoden, mit denen man effizient beliebige Teile der Datei löschen oder in die Datei einfügen könnte. Tatsächlich ist der Wunsch nach einfachen Einfüge- und Löschoperationen der Auslöser für die Schaffung von Datenbankfunktionen wie die DBM-Funktionen oder von ganzen Datenbanken wie MySQL gewesen. Wenn man auf diese Sorte Problem trifft, sollte man also intensiv über den Einsatz von DBM-Dateien oder Datenbanken nachdenken. Um in einer Datei eine Zeile einzufügen oder zu löschen, muß man die Datei öffnen und zeilenweise durchlesen und in eine zweite Datei schreiben. Erreicht man die gewünschte Position, muß man dort eine Zeile einfügen oder löschen. Nach Abschluß der Operation ist die Originaldatei zu löschen und die neue Datei umzubenennen. Dabei ist zu beachten, daß in einer Webumgebung ohne weiteres mehrere Benutzer zugleich eine solche Operation für dieselbe Datei anfordern können. Man muß also auch durch Locking dafür Sorge tragen, daß sich diese Benutzer nicht in die Quere kommen. _________________________________________________________________ # Shared lock auf die Quelldatei $old = fopen($oldfile, "r"); flock($old, 1) or die("Kann die Quelldatei $oldfile nicht locken."); # Exclusive lock auf die Zieldatei $new = fopen($oldfile.".new", "w"); flock($new, 2) or die("Kann die Zieldatei $newfile nicht locken."); $lineno = 0; while($line = fgets($old, 1024)): if ($lineno++ == $zielzeile) continue; # Zeile auslassen fputs($new, $line); endwhile; fclose($old); # Gibt das Lock automatisch auf # Alte Datei wegwerfen. unlink($oldfile); # Neue Datei umbenennen. # (In Windows müssen das rename() und das fclose($new) # vertauscht werden, da es nicht möglich ist, in Windows # eine offene Datei umzubenennen. rename($oldfile.".new", $oldfile); # Neue Datei schließen und dabei Lock aufgeben. fclose($new); _________________________________________________________________ 10.5. Wie kann ich eine Datei zeilenweise rückwärts auslesen? | [1248]Datei | [1249]Anfang | [1250]Ende | [1251]lesen | [1252]Zeile | [1253]Datenbank | [1254]rueckwaerts | Antwort von [1255]Johannes Frömter Dateien sind nicht in "Zeilen" organisiert, die Zeilenende-Zeichen ( \n und/oder \r ) sind ganz gewöhnliche Bytes im Datenstrom. Für das Lesen vom Dateianfang bis zum nächsten Zeilenende-Zeichen gibt es entsprechende Funktionen ( [1256]fgets() ), in umgekehrter Richtung ist dies jedoch wegen der physikalischen Organisation des Dateisystems nicht sinnvoll möglich. Man muß die Datei also zuerst vorwärts zeilenweise in ein Array einlesen - das erledigt die Funktion [1257]file() - und kann dann auf dieses Array beliebig, z.B. auch von hinten nach vorne, zugreifen. Beim Schreiben müssen die "Zeilen" nicht umständlich vorne in die Datei eingefügt, sondern können einfach hinten an die Datei angehängt werden; hierzu wird die Datei im Append-(Anhängen)-Modus geöffnet: _________________________________________________________________ \n"; } // $array von hinten nach vorne durchlaufen $i = sizeof($array); while ($i--) { echo trim($array[$i]) . "
\n"; } ?> _________________________________________________________________ [1258]file() liest die Zeilen einschließlich der Zeilenende-Zeichen ein; da die letzte Zeile in der Datei nicht unbedingt ein Zeilenende-Zeichen hat, sollte [1259]trim() bzw. [1260]rtrim() angewendet werden, um einheitliche Zeilen zu bekommen. 10.6. Wie kann ich einen Datei-Upload per FTP durchführen? | [1261]Datei | [1262]Upload | [1263]FTP | [1264]Modul | [1265]Formular | [1266]HTTP | Antwort von [1267]Johannes Frömter PHP hat eingebaute [1268]FTP-Funktionen , mittels derer man Dateien von oder zu einem FTP-Server übertragen kann. Es ist damit aber nicht möglich, Daten von einem Browser zu empfangen! Dies ist also keine Alternative zum HTTP-Upload per HTML-Formular (siehe hierzu " [1269]Wie funktioniert ein Datei-Upload über HTML-Formulare? "). PHP kann nur einen FTP- Client darstellen, der sich zu einem FTP- Server verbindet, d.h. der Verbindungsaufbau und die -steuerung müssen immer vom PHP-Script aus erfolgen, und die Daten für den Upload müssen sich bereits auf dem Webserver befinden. Vollständige Beispiele für eine FTP-Verbindung mit PHP finden sich im [1270]Manual und weiter unten auf dieser Seite. Antwort von [1271]Kristian Köhntopp Die FTP-Funktionen sind verfügbar, wenn PHP mit der Option -enable-ftp (bei PHP 3: -with-ftp ) übersetzt worden ist. In der Ausgabe von [1272]phpinfo() erscheint dann in der Modulliste der FTP-Support. Die Funktionen sind im Manual im Kapitel [1273]FTP-Funktionen im Einzelnen beschrieben. Vollständiges Beispiel: _________________________________________________________________

FTP Test

\n", $k, $v); } } $size = ftp_size($link, "beispiel"); if ($size < 0) die("Kann die Größe der Datei beispiel nicht bestimmen."); $mtime = ftp_mdtm($link, "beispiel"); if ($mtime < 0) die("Kann die mtime der Datei beispiel nicht bestimmen."); printf("beispiel - %d Byte, %s
\n", $size, strftime("%c", $mtime)); $result = ftp_get($link, "/tmp/bbb", "beispiel", FTP_BINARY); if (!$result) die("Download von Datei beispiel fehlgeschlagen."); if (!ftp_chdir($link, "/incoming")) die("Kann nicht in das Zielverzeiczhnis /incoming wechseln."); $result = ftp_put($link, "upload.txt", "/etc/termcap", FTP_BINARY); if (!$result) die("Upload von Datei termcap fehlgeschlagen."); ftp_quit($link); printf("Ende.
\n"); ?> _________________________________________________________________ 10.7. Unix: Welche Zugriffsrechte brauche ich, um eine Datei anzulegen? | [1274]Datei | [1275]Recht | [1276]neu | [1277]anlegen | [1278]Unix | [1279]Benutzer | Antwort von [1280]Kristian Köhntopp Um in Unix eine Datei anlegen zu können, benötigt ein Programm x-Rechte an jedem Verzeichnis entlang des Pfadnamens sowie w-Recht an dem Verzeichnis, in dem die Datei angelegt werden soll. w-Recht an einem Verzeichnis berechtigt nicht nur dazu, eine Datei neu anzulegen, sondern es berechtigt außerdem dazu, die Namen existierender Dateien aus dem Verzeichnis zu entfernen. Dies wird in Unix oft vereinfachend als "das Löschen einer Datei" bezeichnet. Beim Entfernen von Namen aus einem Verzeichnis werden die Rechte an der Datei und ihr Eigentümer nicht geprüft. Dies ändert sich, setzt man an dem Verzeichnis, in dem sich die Datei befindet, zusätzlich das t-Recht. In diesem Fall können nur der Superuser (genauer: jeder Prozeß der CAP_FOWNER Capability hat), der Eigentümer des Verzeichnisses oder der Eigentümer der Datei noch den Namen aus dem Verzeichnis entfernen. Um eine existierende Datei zu überschreiben, ist dagegen x-Recht an jedem Verzeichnis entlang des Pfadnamens notwendig, sowie w-Recht an der zu verändernden Datei (oder man hat CAP_FOWNER Capability). In Apache mit suexec ist es so, daß CGI-Programme mit derjenigen User-ID und Group-ID ausgeführt werden, die für den virtuellen Webserver mit Hilfe der Direktiven User und Group definiert worden sind. CGI-PHP legt also Dateien mit diesen User- und Group-Rechten an und das Zielverzeichnis muß entsprechende Rechte besitzen, damit ein [1281]fopen() mit dem Anlegen einer Datei erfolgreich sein kann. In Apache mit mod_php ist es so, daß der PHP-Interpreter als Bestandteil des Webservers läuft und mit den Rechten des Webservers Dateien anlegt oder löscht. In diesem Fall ist ausschlaggebend, was die globale (serverweite) User - und Group -Direktive für den Webserver festlegt. Das Zielverzeichnis muß für diesen Unix-Benutzer passende Rechte anbieten. In PHP kann es sein, daß zusätzliche Beschränkungen gelten, die in Kraft treten, falls [1282]safe_mode aktiviert ist. Dies kann in CGI PHP in der php.ini geschehen, in mod_php zusaetzlich in -Blöcken in der zentralen httpd.conf , jedoch nicht in .htaccess -Dateien. Es ist empfehlenswert, für jeden virtuellen Webserver eine gesonderte CGI-Identität anzulegen und jedem virtuellen Webserver ein Verzeichnis einzuräumen, das keine URL hat (nicht unterhalb der Document Root liegt) und das nur durch diese CGI-Identität beschreibbar ist. Auf diese Weise kann jeder virtuelle Webserver anwendungsspezifische Daten auf eine Weise speichern, die nicht durch das http-Protokoll erreichbar ist und die nicht durch andere CGI-Programme anderer virtueller Webserver gelesen oder geschrieben werden koennen. Wird mod_php verwendet, ist ein solcher Schutz nur mit [1283]safe_mode erreichbar. Dies birgt jedoch andere Nachteile. 10.8. Wie kann ich mit PHP auf die serielle Schnittstelle zugreifen? | [1284]Schnittstelle | [1285]lesen | [1286]schreiben | [1287]Zugriff | [1288]seriell | [1289]tty | [1290]extern | [1291]Server | [1292]Client | [1293]Unix | Antwort von [1294]Matthias P. Wuerfl Da PHP serverseitig interpretiert und ausgeführt wird, ist es nicht möglich, mit PHP auf die serielle Schnittstelle eines Clients zuzugreifen. Allerdings besteht die Möglichkeit, auf die seriellen Schnittstellen des Computers zuzugreifen, auf dem das Script ausgeführt wird. Hierzu müssen jedoch die entsprechenden Rechte gesetzt sein, so daß der PHP-Interpreter oder der Webserver auch darauf zugreifen dürfen. Windows und Unixe stellen die serielle Schnittstelle im Filesystem zur Verfügung und man kann mit den normalen Dateisystemfunktionen darauf zugreifen. Um Daten an die serielle Schnittstelle zu senden, genügt schon ein sehr kleines Script: _________________________________________________________________ $string = "Hallo Schnittstelle!\n"; $pointer = fopen("/dev/ttyS0","w"); fwrite ($pointer, $string); fclose($pointer); _________________________________________________________________ In diesem Beispiel wird die serielle Schnittstelle /dev/ttyS0 zum Schreiben geöffnet, ein String wird hineingeschrieben und die Schnittstelle wird wieder geschlossen. Analog dazu könnte man die Schnittstelle auch zum Lesen öffnen und mit [1295]fgets() auf Input warten. Hierbei ist allerdings zu beachten, daß das Script nicht weiterarbeitet, bis nicht eine Zeile eingelesen ist. Können von der seriellen Schnittstelle keine Daten gelesen werden, so stoppt das Script beim [1296]fgets() und wartet bis zum Timeout. Die serielle Schnittstelle kann nicht zum gleichzeitigen Lesen und Schreiben geöffnet werden; jedoch kann ein Script lesend darauf zugreifen, während ein anderes schreibt. Da die seriellen Schnittstellen einen bunten Strauß an Konfigurationsmöglichkeiten kennen, ist die Konfiguration der Schnittstelle auf Betriebssystemebene mitunter etwas kniffelig. Bevor man mit PHP darauf zugreift, sollte man seine Konfiguration mit den Mitteln des Betriebssystems testen, um bei der Erstellung eines PHP-Scriptes eine falsche Schnittstellenkonfiguration als Grund für das Nichtfunktionieren auszuschließen. 10.9. Warum funktioniert unlink() unter Windows nicht? | [1297]Datei | [1298]loeschen | [1299]unlink | [1300]Windows | [1301]Fehler | Antwort von [1302]Martin Jansen In einigen älteren Versionen von PHP 4 ist die Funktion [1303]unlink() unter Windows nicht implementiert, was sich in der Fehlermeldung _________________________________________________________________ Warning: unlink() is not supported in this PHP build in ... on line ... _________________________________________________________________ ausdrückt. Abhilfe schafft hier ein Update auf eine aktuelle Version von PHP 4. 10.10. Wie wende ich include() mit verschachtelten Verzeichnissen an? | [1304]Datei | [1305]einbinden | [1306]include | [1307]Verzeichnis | [1308]Pfad | [1309]relativ | [1310]Konstante | Antwort von [1311]Johannes Frömter Mit include("/subdir/foo.php") bindet man ein Script in einem untergeordneten Verzeichnis ein. In foo.php will man nun die ebenfalls in /subdir stehende Datei bar.php einbinden. Ein einfaches include("bar.php") funktioniert nicht, da PHP die Pfadangabe nicht relativ zum Script mit der [1312]include() -Anweisung, sondern relativ zum Ursprungsscript benötigt. Mit Hilfe der Konstanten __FILE__ kann man sich den richtigen Pfad zusammenschrauben: _________________________________________________________________ include(dirname(__FILE__)."/bar.php"); _________________________________________________________________ 11. Datums- und Kalenderprobleme 11.1. [1313]Wie kann ich das aktuelle Datum bekommen? 11.2. [1314]Wie kann ich ein deutsches Datum in MySQL-Format umwandeln (und umgekehrt)? 11.3. [1315]Wie kann ich die Anzahl der Tage zwischen zwei Daten bestimmen? 11.4. [1316]Wie kann ich das Datum des Vortages bestimmen? 11.5. [1317]Wieviel Tage hat der aktuelle Monat? 11.6. [1318]Wie kann ich die Datumsausgabe auf Deutsch umstellen? 11.1. Wie kann ich das aktuelle Datum bekommen? | [1319]time | [1320]strftime | [1321]date | Antwort von [1322]Kristian Köhntopp Mit Hilfe der Funktion [1323]time() bekommt man den Unix-Timestamp, der sich mit anderen Funktionen ( [1324]strftime() )weiterverarbeiten läßt. Mit der Funktion [1325]date() kann man das Datum direkt als String formatiert abrufen. 11.2. Wie kann ich ein deutsches Datum in MySQL-Format umwandeln (und umgekehrt)? | [1326]mysql | [1327]Formatwandlung | [1328]ISO-8601 | [1329]Datumsformat | Antwort von [1330]Kristian Köhntopp MySQL verarbeitet Datumsangaben im [1331]ISO-8601-Format (siehe [1332]die Abhandlung von Markus Kuhn zu diesem Thema). Dies ist das offizielle deutsche Datumsformat, eine Umwandlung ist nicht notwendig, weil nicht normgerecht. Dennoch kann man das Datum auch in anderen Formaten bekommen. Wahlweise kann die Umwandlung bereits in MySQL oder erst in PHP geschehen. In MySQL kann man mit Hilfe der Funktion date_format das Datum in beliebigen Formaten bekommen: _________________________________________________________________ mysql> select date_format(changed, '%d.%m.%Y %H:%i:%s') as datum -> from teiln_liste; +---------------------+ | datum | +---------------------+ | 27.08.1998 12:49:29 | | 14.12.1999 15:05:52 | | 13.12.1999 08:30:43 | | 13.05.1998 15:51:45 | | 06.10.1998 14:30:25 | | 07.08.1998 11:28:59 | | 23.06.1998 17:15:16 | | 14.01.1999 08:34:22 | | 07.01.2000 11:36:42 | | 01.02.1999 08:47:25 | +---------------------+ 10 rows in set (0.00 sec) _________________________________________________________________ In PHP3 kann man mit Hilfe der Funktion [1333]date() aus einem time_t ein beliebiges Datum generieren und mit Hilfe der Funktion [1334]mktime() aus den Fragmenten einer Datumsangabe einen time_t erzeugen. Mit den time_t (Sekunden seit Mitternacht GMT, 1. Januar 1970) lassen sich sehr natürlich Zeitdifferenzen bestimmen und andere Zeitrechnungen ausführen. Will man dagegen nur einen MySQL TIMESTAMP oder DATE in andere Reihenfolge umsortieren, kann man stattdessen mit den Funktionen [1335]explode() oder [1336]substr() arbeiten. _________________________________________________________________ ### # # date_mysql2german - wandelt ein MySQL-DATE (ISO-Date) # in ein traditionelles deutsches Datum um. # function date_mysql2german($datum) { list($jahr, $monat, $tag) = explode("-", $datum); return sprintf("%02d.%02d.%04d", $tag, $monat, $jahr); } ### # # date_german2mysql - wandelt ein traditionelles deutsches Datum # nach MySQL (ISO-Date). # function date_german2mysql($datum) { list($tag, $monat, $jahr) = explode(".", $datum); return sprintf("%04d-%02d-%02d", $jahr, $monat, $tag); } ### # # timestamp_mysql2german - wandelt ein MySQL-Timestamp # in ein traditionelles deutsches Datum um. # function timestamp_mysql2german($t) { return sprintf("%02d.%02d.%04d", substr($t, 6, 2), substr($t, 4, 2), substr($t, 0, 4)); } _________________________________________________________________ 11.3. Wie kann ich die Anzahl der Tage zwischen zwei Daten bestimmen? | [1337]Intervall | [1338]Zeitdauer | [1339]Julianisches Datum | [1340]GregorianToJD | Antwort von [1341]Kristian Köhntopp Dazu gibt es verschiedene Lösungsansätze. Beispielsweise kann man beide Daten in Julianische Tage verwandeln und sie dann voneinander subtrahieren. Das geschieht mit der Funktion [1342]GregorianToJD() aus der optionalen Kalenderbibliothek von PHP (muss erst kompiliert werden). Eine andere mögliche Vorgehensweise ist es, die Daten in Timestamps umzuwandeln und dann voneinander abzuziehen. Marcus Schwarz zeigt in einem [1343]kurzen Artikel , wie das gemacht werden kann. 11.4. Wie kann ich das Datum des Vortages bestimmen? | [1344]Gestern | [1345]date | [1346]mktime | Antwort von [1347]Kristian Köhntopp Wenn man [1348]date() und [1349]mktime() kombiniert, kann man Datumsberechnungen durchführen. Die Funktion [1350]mktime() berechnet automatisch den korrekten Wert für Überläufe - der 32.12.1997 wird richtig in 01.01.1998 umgewandelt. So erhält man also auch das Datum des Vortages: _________________________________________________________________ $tstamp = mktime(0, 0, 0, date("m"), date("d")-1, date("Y")); $gestern = date("Y-m-d", $tstamp); // ISO-8601 Format print $gestern; _________________________________________________________________ Beachte: Die Reihenfolge der Parameter in der PHP-Funktion [1351]mktime() entspricht nicht der Reihenfolge der Parameter in der C-Funktion gleichen Namens. 11.5. Wieviel Tage hat der aktuelle Monat? | [1352]Monat | [1353]Monatslaenge | [1354]Februar | Antwort von [1355]Kristian Köhntopp [1356]date() versteht seit PHP 3.0.9 die Option "t", die die Anzahl der Tage im Monat zurückliefert: _________________________________________________________________ $tage = date("t"); _________________________________________________________________ Benutzer älterer Versionen behelfen sich mit _________________________________________________________________ $tstamp = mktime(0, 0, 0, date("m")+1, 0, date("Y")); $tage = date("d", $tstamp); print $tage; _________________________________________________________________ 11.6. Wie kann ich die Datumsausgabe auf Deutsch umstellen? | [1357]Zeit | [1358]Deutsch | [1359]Englisch | [1360]Datum | [1361]Sprache | [1362]Monatsname | [1363]Wochentag | Antwort von [1364]Johannes Frömter Mit der Funktion [1365]setlocale() kann man diverse Länder- und sprachabhängigen Einstellungen vornehmen. Die Sprache wird im Format Sprache_Land bzw. Sprache_Land.Codepage angegeben und ist plattformabhängig; unter Unix/Linux sind zweibuchstabige Länderabkürzungen zu verwenden, also z.B. de_DE für Deutsch/Deutschland oder de_AT für Deutsch/Österreich, Windows dagegen erwartet German_Germany bzw. German_Austria . Die Einstellung für die Kategorie LC_TIME wirkt sich nur auf die Ausgabe von [1366]strftime() , nicht jedoch auf [1367]date() aus. _________________________________________________________________ _________________________________________________________________ Um zu testen, ob ein bestimmter locale-String auf einem System unterstützt wird, kann man den Rückgabewert von [1368]setlocale() auswerten: _________________________________________________________________ if (setlocale(...) === false) // dann hat's nicht funktioniert _________________________________________________________________ Warnung: setlocale() ist nicht thread-safe, d.h. die Einstellungen wirken sich in Multithread-Umgebungen u.U. auch auf andere, parallel laufende Scripte aus! 12. Mail lesen und schreiben 12.1. [1369]Was ist SMTP? 12.2. [1370]Was ist das Domain Name System? 12.3. [1371]Unix: Wie funktioniert der Mailversand? 12.4. [1372]Windows: Wie funktioniert der Mailversand? 12.5. [1373]Windows: Wo finde ich Mailserver, die ich bei mir installieren kann? 12.6. [1374]Wie kann ich eine HTML-Mail versenden? 12.7. [1375]Wie kann ich ein Attachment mit einer Mail versenden? 12.8. [1376]Wie kann ich eine Mail effizient an sehr viele Empfänger versenden? 12.9. [1377]Wie kann ich die Gültigkeit einer Mailadresse testen? 12.10. [1378]Wie kann ich überprüfen, ob eine versendete Mail tatsächlich angekommen ist? 12.11. [1379]Wie kann ich feststellen, ob eine Mailadresse äußerlich gültig ist? 12.12. [1380]Wie versende ich SMS mit PHP? 12.13. [1381]Wie kann ich den Absender meiner Mail festlegen? 12.1. Was ist SMTP? | [1382]Mail | [1383]SMTP | [1384]Protokoll | [1385]Grundlagen | Antwort von [1386]Kristian Köhntopp SMTP ist das Simple Mail Transport Protocol , das Protokoll, das im Internet verwendet wird, um Mail bei einem Mailserver einzuliefern und Mail zwischen zwei Mailservern auszutauschen. SMTP ist ein textorientiertes Protokoll, das auf dem TCP-Port 25 abgewickelt wird. Daher kann man es mit dem Kommando telnet leicht simulieren und debuggen. _________________________________________________________________ kris@valiant:~ > telnet mail 25 Trying 193.102.57.5... Connected to white.koehntopp.de. Escape character is '^]'. 220 white.koehntopp.de ESMTP Sendmail 8.9.3/8.9.3/SuSE Linux 8.9.3-0.1 HELO valiant.koehntopp.de 250 white.koehntopp.de Hello valiant.koehntopp.de [193.102.57.3], [..] MAIL FROM: kris@koehntopp.de 250 kris@koehntopp.de... Sender ok RCPT TO: kris@koehntopp.de 250 kris@koehntopp.de... Recipient ok DATA 354 Enter mail, end with "." on a line by itself From: ab@sen.der To: Mailingliste der Empfaenger Subject: Testmail Text der Nachricht . 250 LAA08243 Message accepted for delivery QUIT 221 white.koehntopp.de closing connection Connection closed by foreign host. _________________________________________________________________ Nachdem die Verbindung mit dem empfangenden Mailer hergestellt worden ist, identifiziert sich der absendende Rechner mit dem Kommando HELO . Falls dieser Rechner berechtigt ist, überhaupt Mail einzuliefern, antwortet der empfangende Rechner mit einem Statuscode (hier: 250) und einer Textmeldung. Der Absender nennt jetzt den tatsächlichen Absender ( MAIL FROM ) und den tatsächlichen Empfänger ( RCPT TO , "Receipt to") der Mail. Nach dem Kommando DATA beginnt die Übertragung der eigentlichen Mailnachricht, die mit einem einfachen Punkt abgeschlossen wird (das Protokoll verlangt, daß Punkte am Anfang einer Zeile, die Teil der Nachricht sind, verdoppelt werden). Die Absender und Empfänger im Text der Mail sind bedeutungslos und werden vom Mailer nicht weiter ausgewertet: Entscheidend sind sie nur ganz am Anfang, wenn die Mail auf den Weg gebracht wird, ähnlich wie bei einem Brief, der in einen Umschlag getütet wird. Von Bedeutung sind nur die Absender und Empfänger im MAIL FROM und RCPT TO -Dialog. Diese beiden Adressen nennt man Envelope-From und Envelope-To, nach den Anschriften, die außen auf einem Briefumschlag (Envelope) stehen und für die Zustellung wichtig sind. Dies bewirkt zum Beispiel, daß die Empfänger einer Mailingliste nicht alle in der To: -Zeile des Headers aufgelistet sind: Im To: steht nur der Name der Liste, der Listenroboter (Mailexploder) expandiert diesen Namen jedoch zu einer Liste der Empfänger im Envelope. Der Envelope-From einer Nachricht ist nach der Auslieferung einer Mail in eine Mailbox als From -Header (From-Space im Gegensatz zu From-Doppelpunkt, dem Body-From) ganz am Anfang der Nachricht enthalten. Der Envelope-To ist verschwunden, weil er nicht mehr gebraucht wird - daher ist es nicht möglich, eine einzelne POP3-Mailbox nach dem Download mit Fetchmail oder anderen Werkzeugen korrekt im Hause weiter aufzuteilen: Der tatsächliche Empfänger steht nicht mehr zur Verfügung und die Body-To-Zeilen liefern nur Anhaltspunkte. 12.2. Was ist das Domain Name System? | [1387]Mail | [1388]DNS | [1389]Domain | [1390]Name | [1391]IP | [1392]MX | [1393]Server | [1394]nslookup | [1395]Record | [1396]sendmail | Antwort von [1397]Kristian Köhntopp Das Domain Name System ist eine verteilte Datenbank, die Namen in Domainform ( ein.wort.mit.punkten.darin ) beliebige getypte textuelle Information zuordnen kann. Man kann dem Domain Name System Fragen stellen, die die Form (name, typ) haben und bekommt dann einen oder mehrere Ressource Records des passenden Typs als Antwort. Wichtige Ressource-Records sind A- und AAAA-Records, die die IP-Nummern von Rechnern darstellen, A-Records für IPv4-Adressen und AAAA-Records für IPv6-Adressen. Außerdem sind für die Mailzustellung MX-Records besonders wichtig, weil sie den für den Mailempfang zuständigen Rechner bezeichnen. Ebenfalls häufig findet man PTR-Records (Umwandlung von IP-Nummern zurück in Namen) und NS-Records (Finden von zuständigen Nameservern). Mit Hilfe des Werkzeuges nslookup kann man in Unix und Windows Anfragen an das DNS stellen, außerdem kann man bequemer das Unix-Programm dig verwenden. Ein Mailer wie das Unix-Programm sendmail richtet sich bei der Mailzustellung nach den MX-Records (MX steht für mail exchanger ) einer Domain. _________________________________________________________________ kris@valiant:~ > nslookup Default Server: nuki.netuse.de Address: 193.98.110.1 > set type=mx > php.net. Server: nuki.netuse.de Address: 193.98.110.1 Non-authoritative answer: php.net preference = 10, mail exchanger = php.chek.com Authoritative answers can be found from: php.net nameserver = NS1.EASYDNS.com php.net nameserver = NS2.EASYDNS.com php.net nameserver = REMOTE1.EASYDNS.com php.net nameserver = REMOTE2.EASYDNS.com php.chek.com internet address = 208.247.106.167 NS1.EASYDNS.com internet address = 205.210.42.21 NS2.EASYDNS.com internet address = 205.210.42.22 REMOTE1.EASYDNS.com internet address = 208.247.106.167 REMOTE2.EASYDNS.com internet address = 198.96.119.44 > ^D kris@valiant:~ > kris@valiant:~ > dig php.net mx ; <<>> DiG 8.2 <<>> php.net mx ;; res options: init recurs defnam dnsrch ;; got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 4 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 4, ADDITIONAL: 5 ;; QUERY SECTION: ;; php.net, type = MX, class = IN ;; ANSWER SECTION: php.net. 5h11m23s IN MX 10 php.chek.com. ;; AUTHORITY SECTION: php.net. 1d19h26s IN NS NS1.EASYDNS.com. php.net. 1d19h26s IN NS NS2.EASYDNS.com. php.net. 1d19h26s IN NS REMOTE1.EASYDNS.com. php.net. 1d19h26s IN NS REMOTE2.EASYDNS.com. ;; ADDITIONAL SECTION: php.chek.com. 19h41s IN A 208.247.106.167 NS1.EASYDNS.com. 1d19h23m28s IN A 205.210.42.21 NS2.EASYDNS.com. 1d19h23m28s IN A 205.210.42.22 REMOTE1.EASYDNS.com. 1d19h23m28s IN A 208.247.106.167 REMOTE2.EASYDNS.com. 1d19h23m28s IN A 198.96.119.44 ;; Total query time: 86 msec ;; FROM: valiant to SERVER: default -- 193.102.57.4 ;; WHEN: Mon Feb 14 11:57:54 2000 ;; MSG SIZE sent: 25 rcvd: 221 _________________________________________________________________ Im Beispiel mit nslookup , das so unter Unix und unter Windows NT funktioniert, kann man sehen, daß der lokale Domain Name Server als Datenquelle verwendet wird. Durch das Kommando set type=mx legen wir fest, daß wir Informationen über MX-Records haben möchten. Durch einfaches Eingeben eines Namens (hier: php.net ) wird die Anfrage abgesendet und wir erhalten zwei Sorten von Informationen: * Der lokale Server hat uns die nicht-abschließende Information geliefert, daß Mail an php.net an den Rechner mail.chek.com ausgeliefert werden soll. Wenn wir also eine Mailzustellung an eine Adresse der Form bla@php.net vornehmen sollen, müssen wir uns mit mail.chek.com , Port 25 verbinden und die Mail dort abliefern. * Diese Information ist jedoch nicht-abschließend, weil der lokale Nameserver nicht Eigentümer der Domain php.net ist. Wenn wir abschließende Information wünschen, müßten wir uns an einen der vier definitiven Mailserver für die Domain wenden, die uns genannt werden: NS1.EASYDNS.com , NS2.EASYDNS.com , REMOTE1.EASYDNS.com oder REMOTE2.EASYDNS.com und die Frage dort noch einmal stellen. Die Adressen dieser Server werden uns auch gleich mitgeliefert. Die Ausgabe von dig liefert diese Information noch einmal, nur in besser lesbarer und übersichtlicherer Form. 12.3. Unix: Wie funktioniert der Mailversand? | [1398]Mail | [1399]Unix | [1400]Versand | [1401]sendmail | [1402]Mailserver | Antwort von [1403]Kristian Köhntopp Ein Unix-System hat in der Regel ein Mailprogramm installiert, das Anfragen an das DNS-System stellen kann oder auf andere Weise die zur Auslieferung der Mail benötigten Informationen beschaffen kann. In den meisten Fällen ist dieses Mailprogramm /usr/lib/sendmail (oder /usr/sbin/sendmail ) und versteht eine Option -t , die bewirkt, daß man das Programm starten und eine Mail mit allen benötigten Headern auf der Standardeingabe hinterlassen kann. Das Programm wird dann auf der Basis der erhaltenen Headerinformationen den Envelope generieren, den Header vervollständigen und ggf. korrigieren, bevor die Mail dann abgesendet wird. Der einfachste Weg, um ein Programm zu starten und dann seine Standardeingabe mit Daten zu füllen, ist die C-Funktion popen() . Die PHP-Funktion [1404]mail() verwendet diese C-Funktion, um das durch die Konfigurationsvariable [1405]sendmail_path definierte Mailprogramm zu starten und mit den benötigten Daten zu füttern. _________________________________________________________________ mail("em@pfaeng.er", "Testmail", "Dies ist nur eine Testnachricht.", "From: ab@send.er\nReply-To: devnull@send.er"); _________________________________________________________________ Hinweis: In vielen Versionen von PHP unter Windows funktioniert die Variante von [1406]mail() mit drei Parametern wegen eines Fehlers im Interpreter nicht. In diesem Fall ist die Funktion [1407]mail() zwingend mit vier Parametern aufzurufen, etwa indem als vierter Parameter der Wert Content-Type: text/plain; charset=iso-8859-1\n angegeben wird. Alternativ kann man diese Funktionalität auch manuell in PHP nachprogrammieren, also die PHP-Funktion [1408]popen() aufrufen und die Mail dann ausgeben. Beide Ansätze sind funktional gleich (aber [1409]mail() ist weniger Arbeit). _________________________________________________________________ $fd = popen("/usr/sbin/sendmail -t ","w"); fputs($fd, "To: em@pfaeng.er\n"); fputs($fd, "From: ab@send.er\n"); fputs($fd, "Reply-To: devnull@send.er\n"); fputs($fd, "Subject: Testmail\n\n"); fputs($fd, "Das ist nur eine Testnachricht.\n"); pclose($fd); _________________________________________________________________ 12.4. Windows: Wie funktioniert der Mailversand? | [1410]Windows | [1411]Mail | [1412]Versand | [1413]Mailserver | [1414]SMTP | Antwort von [1415]Kristian Köhntopp In Windows kann man nicht davon ausgehen, daß wie in Unix ein Mailer installiert ist, der Mail selber zustellen kann. Daher muß man dem Windows-System einen Rechner mitteilen, der einen Mailer installiert hat, den das Windows-System über TCP/IP mitbenutzen darf. Das kann der lokale Rechner localhost sein, falls auf dem eigenen System ein Mailserver installiert ist, aber auch jedes andere System, das für uns seinen Relay- und Spamschutz abgeschaltet hat. Die Funktion [1416]mail() baut unter Windows eine TCP/IP-Verbindung zum Port 25 dieses in der Konfigurationsvariable SMTP festgelegten Systems auf und erzeugt mit dem oben gezeigten SMTP-Dialog eine Mail. Dabei wird die in der Konfigurationsvariablen sendmail_from festgelegte Absenderadresse verwendet. Anders als in Unix hat man hier also nicht die Freiheit, den Absender oder Blindkopienempfänger in der Mailfunktion frei definieren zu können, weil der entfernte Mailer diese Daten von uns nicht annimmt oder bei der späteren Headerkorrektur wieder überschreibt. Alternativ kann man die Arbeit der Mailfunktion auch manuell nachprogrammieren - dabei sollte man jedoch bedenken, daß ein SMTP-Server nicht jede Headerzeile so annimmt oder unverfälscht durchläßt, wie man sie ihm vorwirft! Der hier gezeigte Code ist sehr unzuverlässig, denn er hat keine richtige Fehlerprüfung. _________________________________________________________________ $hdr = "From: ab@send.er\r\n"; $hdr .= "To: em@pfaeng.er\r\n"; $hdr .= "Reply-To: devnull@send.er\r\n"; $hdr .= "Subject: Testmail\r\n"; $hdr .= "\r\n"; # Socket oeffnen. $fp = fsockopen("mail.server.de", 25); $result = fgets($fp, 1024); if ($result+0 != 220) die("Statuscode falsch (service not ready?): $result"); # HELO fputs($fp, "HELO mein.eigener.servername.de\r\n"); $result = fgets($fp, 1024); if ($result+0 != 250) die("HELO Statuscode falsch: $result"); # MAIL FROM fputs($fp, "MAIL FROM: ab@send.er\r\n"); $result = fgets($fp, 1024); if ($result+0 != 250) die("MAIL FROM Statuscode falsch: $result"); # RCPT TO fputs($fp, "RCPT TO: em@pfaeng.er\r\n"); $result = fgets($fp, 1024); if ($result+0 != 250) die("RCPT TO: Statuscode falsch: $result"); # DATA fputs($fp, "DATA\r\n"); $result = fgets($fp, 1024); if ($result+0 != 354) die("DATA: Statuscode falsch: $result"); # Header senden fputs($fp, $hdr); # Text senden fputs($fp, "Das ist nur eine Testnachricht."); # Ende von DATA: CRLF . CRLF fputs($fp, "\r\n.\r\n"); $result = fgets($fp, 1024); if ($result+0 != 250) die("DATA(end): Statuscode falsch: $result"); # QUIT fputs($fp, "QUIT\r\n"); $result = fgets($fp, 1024); if ($result+0 != 221) die("QUIT: Statuscode falsch: $result"); # Verbindung schließen fclose($fp); _________________________________________________________________ Wer sich diesen Code nicht zutraut, kann auch die [1417]SMTP Klasse von Manuel Lemos verwenden. Sie verbirgt das manuelle Senden der Daten über den Socket gegenüber dem Programmierer und erzeugt auch Fehlermeldungen, wenn mal etwas nicht klappte. Beispiel: _________________________________________________________________ host_name=getenv("HOSTNAME"); $smtp->localhost="localhost"; $from = "name@".$smtp->host_name; $to = "da@irgendwo.de"; $inhalt = "Inhalt der Mail"; if ($smtp->SendMessage( $from, array($to), array("From: $from","To: $to","Subject: test"), $inhalt)) { print "Mail wurde erfolgreich versandt."; } ?> _________________________________________________________________ 12.5. Windows: Wo finde ich Mailserver, die ich bei mir installieren kann? | [1418]Mailserver | [1419]Windows | [1420]Mail | [1421]Installation | Antwort von [1422]Kristian Köhntopp Wenn man in Windows keinen eigenen Mailserver hat, den man zum Mailversand nutzen kann, muß man sich einen installieren. Die folgenden Produkte wurden in der Newsgroups zu diesem Zweck genannt: * [1423]Argosoft Mailserver * [1424]Hamster 12.6. Wie kann ich eine HTML-Mail versenden? | [1425]mail | [1426]html | [1427]MIME | Antwort von [1428]Kristian Köhntopp Die Mailfunktion in PHP hat einen optionalen vierten Parameter, mit dem man zusätzliche Headerzeilen definieren kann. Diese Headerzeilen können den MIME-Type einer Mail bestimmen, den Absender der Mail festlegen oder auch beliebige Nicht-Standard-Header ( X-Mailer: etc.) enthalten. _________________________________________________________________ $message = "

Hello world!

"; $to = "empfaenger@system.de"; $subject = "Betrefftext"; $xtra = "From: ab@sender.de (Ab Sender)\n"; $xtra .= "Content-Type: text/html\nContent-Transfer-Encoding: 8bit\n"; $xtra .= "X-Mailer: PHP ". phpversion(); mail($to, $subject, $message, $xtra); _________________________________________________________________ 12.7. Wie kann ich ein Attachment mit einer Mail versenden? | [1429]Mail | [1430]Attachment | [1431]Datei | [1432]versenden | Antwort von [1433]Kristian Köhntopp Bei der [1434]mail() -Funktion von PHP kann man im vierten Argument jeden beliebigen zusätzlichen Header angeben. Attachments werden MIME-kodiert. Eine [1435]frei verfügbare Mail-Klasse kapselt diese Funktionalität und macht die ganze Geschichte recht einfach. Wer die Datei, die attached werden soll, per Eingabeformular auswählen will, sollte folgenden Weg nehmen: Wo vorher _________________________________________________________________ $attachment = fread(fopen("test.jpg", "r"), filesize("test.jpg")); [...] $mail->add_attachment("$attachment", "test.jpg", "image/jpeg"); _________________________________________________________________ verwendet wurde, muß nach einem Dateiupload _________________________________________________________________ $attachment = fread(fopen($userfile, "r"), $userfile_size); [...] $mail->add_attachment($attachment, $userfile_name, $userfile_type); _________________________________________________________________ verwendet werden. Dabei ist $userfile gegen den Namen von auszutauschen. Antwort von [1436]Johannes Frömter Von Richard Heyes gibt es mit der [1437]HTML Mime Mail class eine weiterentwickelte und stark erweiterte Version dieser Klasse. 12.8. Wie kann ich eine Mail effizient an sehr viele Empfänger versenden? | [1438]Mail | [1439]versenden | [1440]Empfaenger | [1441]viele | [1442]mehrere | [1443]bulk | [1444]Massenversand | [1445]Newsletter | [1446]CC | [1447]BCC | Antwort von [1448]Kristian Köhntopp Am günstigsten und sichersten versendet man Mail an viele Empfänger, indem man eine spezialisierte Software dafür verwendet. Empfehlenswert sind Mailinglisten-Server wie [1449]majordomo , [1450]ezmlm oder [1451]Listar . Alternativ kann man sich mit einer deutlich primitiveren Lösung in PHP behelfen, indem man gemäß den Beispielen oben zusätzliche Headerzeilen mit Bcc -Empfängern erzeugt. Auf diese Weise generiert man eine einzelne Mail an viele Empfänger, die vom Mailer sehr effizient verteilt werden kann. Gleichzeitig vermeidet man durch die Verwendung von blind carbon copy (BCC)-Empfängern, daß die Empfänger im Kopf der Mail mit aufgeführt werden und auf diese Weise ein Monsterheader entsteht. _________________________________________________________________ # Empfaengerliste $empfaenger = array("a@system.de", "b@system.de"); # Bcc generieren reset($empfaenger); while(list($k, $v) = each($empfaenger)) { $bcc .= "Bcc: $v\n"; } mail("em@pfaeng.er", "Testmail", "Dies ist nur eine Testnachricht.", $bcc); _________________________________________________________________ 12.9. Wie kann ich die Gültigkeit einer Mailadresse testen? | [1452]Mail | [1453]Empfaenger | [1454]Adresse | [1455]Domain | [1456]pruefen | [1457]gueltig | [1458]falsch | [1459]bounce | [1460]unzustellbar | Antwort von [1461]Kristian Köhntopp Der Mailer eines Systems kann die Mail dann zustellen, wenn das Domain Name System (DNS) für die Zieladresse einen Mail Exchanger (MX) Ressource Record (RR) oder einen Address (A) Ressource Record enthält. Wenn man testen möchte, ob die Empfängeradresse für eine Mail gültig ist, braucht man Zugriff auf das Internet und einen DNS-Server, den man befragen kann. Dann kann man die Anfrage, die der Mailer später einmal stellen wird, um die Mail zuzustellen, manuell mit Hilfe der Funktion [1462]checkdnsrr() nachvollziehen. Die Funktion liefert true , wenn ein passender RR vorhanden ist. Eine DNS-Anfrage kann je nach Verfügbarkeit des DNS-Systems bis zu mehreren Minuten dauern. Der betreffende Webserverprozeß ist in diesem Zeitraum blockiert. Das Vorhandensein der benötigten RRs garantiert natürlich nicht, daß das Zielsystem auch mit uns redet oder daß der gewünschte Benutzer existiert und Mail empfangen kann. Die einzige Methode, zuverlässig zu testen, ob eine Mail zustellbar ist, ist sie zuzustellen. _________________________________________________________________ $addr = "user@host.doma.in"; list($user, $host) = explode("@", $addr); if (checkdnsrr($host, "MX") or checkdnsrr($host, "A")) { print "Mail ist vielleicht zustellbar.
\n"; } else { print "Mail ist sicher nicht zustellbar.
\n"; } _________________________________________________________________ Antwort von Guido Haeger Im SIMPLE MAIL TRANSFER PROTOCOL (SMTP - [1463]RFC 0821 ) ist das Kommando VERIFY (VRFY) definiert. Hat man die Mailadresse "demouser@domain.tld" dann kann man theoretisch mittels "VERIFY demouser" beim für "domain.tld" zuständigen SMTP-Server anfragen, ob ein Postfach für "demouser" existiert. In der Praxis ist dieses Kommando aber bei fast allen SMTP-Servern deaktiviert, bzw. überhaupt nicht implementiert (Spam-/Datenschutz), so daß VERIFY keine praktikable Lösung ist. 12.10. Wie kann ich überprüfen, ob eine versendete Mail tatsächlich angekommen ist? | [1464]Mail | [1465]Versand | [1466]Empfaenger | [1467]angekommen | [1468]empfangen | [1469]pruefen | [1470]Zustellung | Antwort von [1471]Kristian Köhntopp Manche Mailer unterstützen Delivery Status Notification (DSN) nach [1472]RFC 1894 . Dies ist ein RFC auf der Internet Standards Track im Status proposed standard , er wird also in veränderter Form einmal Draft Standard und dann Internet Standard werden. Relevant im selben Zusammenhang ist die ganze Reihe der RFCs in diesem Bereich: * [1473]RFC 1891 ( SMTP Service Extension for Delivery Status Notifications ) * [1474]RFC 1892 ( The Multipart/Report Content Type for the Reporting of Mail System Administrative Messages ) * [1475]RFC 1893 ( Enhanced Mail System Status Codes ) * und der bereits genannte [1476]RFC 1894 ( An Extensible Message Format for Delivery Status Notifications ) Die Anforderung von DSNs erfolgt mit Hilfe der in RFC 1891 beschriebenen SMTP Service Extension, ist also Bestandteil des SMTP Dialoges. Auf Unix-Systemen wird der SMTP-Dialog durch das lokal installierte sendmail -Programm abgewickelt. Dieses versteht bestimmte Optionen ( -N und -R , die in der Manualpage von sendmail beschrieben sind), mit deren Hilfe DSNs angefordert werden können. Die Option -N legt fest, für welche Fälle DSNs erzeugt werden sollen. Es können mehrere Reportklassen durch Komma getrennt spezifiziert werden: failure , wenn eine Benachrichtigung bei Zustellproblemen erzeugt werden soll, delay für eine Benachrichtigung bei Zustellverzögerungen und success , für Nachricht, wenn die Zustellung erfolgreich war. Mit der Zustellbenachrichtigung wird zugleich ein Teil der Originalnachricht zurück übermittelt, damit der Absender feststellen kann, auf welche Nachricht sich die Zustellbenachrichtigung bezieht. Die Option -R legt fest, wieviel von der Originalnachricht zurück übermittelt werden soll: hdrs übermittelt nur die Headerzeilen der Originalnachricht zurück, während full die komplette Originalnachricht in der Zustellbenachrichtigung zurückgibt. Auf Unix-Systemen kann man also DSNs anfordern, wenn man einen ausreichend neuen Sendmail (8.8.x oder besser) installiert hat und man die Konfigurationsvariable sendmail_path passend setzt: _________________________________________________________________ sendmail_path = /usr/lib/sendmail -N failure,success -R hdrs -t _________________________________________________________________ Auf Windows-Systemen wickelt PHP den SMTP-Dialog mit einem entfernten SMTP-Mailer ab. Es sind keine Eingriffsmöglichkeiten vorgesehen, mit deren Hilfe man den SMTP-Dialog erweitern kann. Entscheidet man sich, den SMTP-Dialog manuell abzuwickeln, damit man die SMTP-Erweiterungen gemäß RFC 1891 implementieren kann, ist unbedingt darauf zu achten, daß man das Vorhandensein dieser Erweiterungen im EHLO -Kommando des SMTP-Dialoges richtig abfragt, sonst schlägt die Mailzustellung fehl, wenn man auf einem Mailer einliefert, der kein DSN kann. Das Resultat von aktivierten DSNs sind Nachrichten mit dem Mime-Typ multipart/report wie in RFC 1892 und RFC 1894 spezifiziert. Diese Nachrichten enthalten mindestens zwei, meist jedoch drei Teile, von denen einer message/delivery-status ist und eine maschinenlesbare DSN nach RFC 1894 enthält. Mit Hilfe eines POP3 oder IMAP-Clients kann man diese Nachrichten einsammeln und analysieren. 12.11. Wie kann ich feststellen, ob eine Mailadresse äußerlich gültig ist? | [1477]Mail | [1478]Adresse | [1479]Domain | [1480]gueltig | [1481]gefaelscht | [1482]Empfaenger | [1483]pruefen | Antwort von [1484]Kristian Köhntopp Durch einen geeignet gewählten regulären Ausdruck. Ein einfacher solcher Ausdruck hätte zum Beispiel die Form _________________________________________________________________ if (!eregi("^[_\.0-9a-z-]+@([0-9a-z][0-9a-z-]+\.)+[a-z]{2,3}$",$email)): # Mailadresse sieht seltsam aus endif; _________________________________________________________________ Dieser Ausdruck ist jedoch gleichzeitig zu locker und zu restriktiv gewählt: Einige Mailsysteme mögen zum Beispiel weitere Zeichen im Empfängernamen zulassen oder ablehnen und möglicherweise gibt es Topleveldomains mit einer anderen Buchstabenanzahl als 2 oder 3. Einige Absender haben vielleicht auch eine exotischere Mailadresse der Form bla!user@host.dom oder bla%host1.dom@host2.dom . Alle diese legalen Adressen werden von dem o.a. Ausdruck abgelehnt. 12.12. Wie versende ich SMS mit PHP? | [1485]Mail | [1486]SMS | [1487]Versand | [1488]Gateway | [1489]Handy | Antwort von [1490]Martin Jansen Generell muss man bei der Verwendung von SMS zwei Arten unterscheiden: Auf der einen Seite gibt es die Möglichkeit, eine SMS an eine vorher definierte Rufnummer (z.B. die des eigenen Handys) zu senden. Die zweite Möglichkeit, die sehr häufig von Portalseiten (z.B. Lycos) genutzt wird, ist, dem Besucher der Seite die Möglichkeit zu geben, an eine Rufnummer seiner Wahl eine SMS zu senden. Diese SMS enthalten allerdings in der Regel einen kurzen Werbetext. Variante 1 lässt sich mit der [1491]mail() -Funktion von PHP realisieren: Jeder Netzanbieter stellt jedem seiner Kunden auf Nachfrage eine eigene E-Mail-Adresse (z.B. @smsmail.eplus.de) zur Verfügung. Nachrichten an diese E-Mail-Adresse werden als SMS an den Mobilfunkaccount des Kunden weitergeleitet. Dieser Service kostet je nach Anbieter zwischen 0,29 DM und 0,39 DM pro SMS. So würde zum Beispiel folgendes Skript eine SMS an die Nummer 0177-1234567 senden: _________________________________________________________________ _________________________________________________________________ Mit dieser Variante lässt sich auf einfache Weise der Versand von SMS an eine vorher definierte Nummer (i.d.R. die eigene) realisieren. Ein Risiko, das man nicht unterschätzen sollte, ist die Gefahr des Missbrauchs: Ein böswilliger User sendet zum Beispiel 100 SMS in Folge und verursacht somit beim Empfänger auf einen Schlag Kosten von etwa 30-40 DM. Hier müssen entsprechende Sicherheitsmechanismen wie zum Beispiel ein Limit an SMS pro IP oder Cookies ansetzen, die aber alle keine 100%-ige Sicherheit garantieren. Die zweite Möglichkeit verwendet eine vollwertige SMS-Gateway, um den Versand von SMS an beliebige Nummern zu realiseren. Die SMS-Gateway ist entweder lokal auf dem eigenen Server installiert oder man verwendet einen Fremdanbieter, der eine SMS-Gateway zu entsprechenden Konditionen zur Verfügung stellt. Nachrichten, die über die SMS-Gateway versendet werden sollen, werden nach dem gleichen Prinzip wie Variante 1 mit der PHP-Funktion [1492]mail() versendet. Der Unterschied besteht darin, dass neben dem Text der SMS auch entsprechende Steuersignale im E-Mail-Text enthalten sind, die für die SMS-Gateway die entsprechenden Informationen liefern, wohin die SMS gesendet werden soll. Bei der Verwendung eines Fremdanbieters sind des weiteren die nötigen Authentifizierungsdaten in der E-Mail enthalten, die den SMS-Versand ermöglichen. Selbstverständlich birgt auch diese Variante die Möglichkeit des Missbrauchs, weshalb auch hier die gleichen Sicherheitsmechanismen wie bei Variante 1 angewendet werden müssen. Die meisten der grossen Anbieter von kostenlosem SMS-Versand reduzieren so zum Beispiel die Anzahl Nachrichten, die pro IP gesendet werden dürfen, auf eine gewisse Anzahl pro Tag. 12.13. Wie kann ich den Absender meiner Mail festlegen? | [1493]mail | [1494]from | [1495]Absender | [1496]Return | [1497]Path | [1498]Envelope | [1499]bounce | [1500]unzustellbar | [1501]Server | [1502]Administrator | [1503]sendmail | [1504]Header | [1505]Adresse | Antwort von [1506]Johannes Frömter Um im Mailprogramm des Empfängers einen bestimmten Absender anzuzeigen, muß in der Mail der From: -Header entsprechend gesetzt sein. Die Funktion [1507]mail() nimmt im vierten Parameter solche Header-Angaben entgegen: _________________________________________________________________ mail("empfaenger@example.org", "Betrefftext", "Hello world!", "From: Absender "); _________________________________________________________________ Eine Mailadresse mit Namen gibt man am besten in der Form Name ; an. Weitere Headerzeilen sind durch einen Zeilenumbruch ( \n ) zu trennen. Durch diesen Header wird nur der Absender in der Mail, nicht jedoch der Envelope-From: - quasi die Absenderangabe auf dem "Briefumschlag" der Mail - festgelegt. Nicht zustellbare Mails gehen daher an den Serveradministrator, und nicht an den tatsächlichen Absender zurück. Ab PHP Version 4.0.5 kennt mail() hierzu einen fünften Parameter, dessen Inhalt direkt an das Mailprogramm weitergereicht wird. Im Falle des üblichen sendmail übergibt man den Absender für das Envelope-From: folgendermaßen: _________________________________________________________________ mail("empfaenger@example.org", "Betrefftext", "Hello world!", "From: Absender "), "-f absender@example.com"); _________________________________________________________________ Normalerweise fügt sendmail dann auch ein X-Authentication-Warning in den Header ein; um diese Warnung zu unterdrücken, sollte man den User, unter dessen Account der Webserver läuft, zu den "trusted users" in der sendmail-Konfiguration hinzufügen. 13. Datenbanken 13.1. [1508]Wie kann ich mehr über SQL lernen? 13.2. [1509]Wieso kann ich mehrere, durch Semikolon getrennte Statements nicht ausführen? 13.3. [1510]Ist es sinnvoll, Bilder in einer Datenbank abzulegen? 13.4. [1511]Windows: Jeder Zugriff auf meine Datenbank dauert eine halbe Minute! 13.5. [1512]Wie kann ich meine Datenbankperformance steigern? 13.6. [1513]Wie kann ich zwei Tabellen miteinander verknüpfen? 13.7. [1514]Was ist Aggregation? Was ist GROUP BY? 13.8. [1515]Was ist der Unterschied zwischen connect und pconnect? 13.9. [1516]Wie kann ich mein Datenbankpaßwort gegen Spionage sichern? 13.10. [1517]MySQL oder PostgreSQL? 13.11. [1518]Wie komme ich bei meinem Provider an die Datenbank? 13.12. [1519]Wie kann ich auf einen ODBC-Server (MSSQL, Access) zugreifen? 13.13. [1520]Wieso wird aus " plötzlich \" und wie geht das wieder weg? 13.14. [1521]Warum soll ich nicht SELECT * schreiben? 13.1. Wie kann ich mehr über SQL lernen? | [1522]SQL | [1523]Datenbank | [1524]lernen | [1525]Tutorial | [1526]Buch | [1527]MySQL | [1528]PostgreSQL | Antwort von [1529]Kristian Köhntopp Bei Markt und Technik gibt es den vollständigen Text von [1530]SQL in 21 Tagen online zu lesen. Man kann das Buch auch kaufen. Guido Stepken hat sein [1531]MySQL Datenbankhandbuch auf seiner Website zum Online-Lesen oder zum Download bereitgestellt. Das Buch " [1532]PostgreSQL ", welches bei Addison-Wesley erschienen ist, kann auch [1533]online gelesen werden. Ein englischsprachiges Einsteiger-Tutorial findet sich unter [1534]www.sqlcourse.com (Teil 1, behandelte Themen: von "Table basics" über "Inserting/Updating/Deleting" bis "Advanced Queries"). [1535]Teil 2 nimmt sich folgende Themen vor: "Aggregate Functions", "GROUP BY/HAVING/ORDER BY", "Table Joins" etc. Nettes Special: ein Online-SQL-Interpreter. Für MySQL-spezifische Fragen gibt es eine eigene deutschsprachige Newsgroup: [1536]de.comp.datenbanken.mysql ( [1537]FAQ dieser NG ) 13.2. Wieso kann ich mehrere, durch Semikolon getrennte Statements nicht ausführen? | [1538]SQL | [1539]Datenbank | [1540]Semikolon | [1541]phpMyAdmin | Antwort von [1542]Kristian Köhntopp SQL kennt keine Mehrfachstatements. Einige SQL-Frontends (der MySQL-Kommandoprozessor, phpMyAdmin) kennen Mehrfachstatements, die sie manuell in einzelne Anweisungen zerlegen und nacheinander an den Datenbankserver senden. PHP selbst macht dies nicht. Man muß seine Statements manuell zerlegen und einzeln nacheinander absenden. Um ein Statement zu zerlegen, ist es nicht ausreichend, auf dieses Statement einfach [1543]explode() anzuwenden. Beispiel: _________________________________________________________________ INSERT INTO table VALUES('foo;bar'); _________________________________________________________________ Wie es richtig geht, kann man im Code von phpMyAdmin nachlesen. Die relevante Stelle ist die Funktion split_sql() in der Datei db_readdump.php . 13.3. Ist es sinnvoll, Bilder in einer Datenbank abzulegen? | [1544]SQL | [1545]Datenbank | [1546]Bild | [1547]Download | [1548]Performance | [1549]BLOB | Antwort von [1550]Kristian Köhntopp Aus irgendeinem Grund scheinen viele Leute zu glauben, daß es Bilddaten adeln würde, wenn man sie in eine Datenbank stopft. Wenn man die Bilddaten selbst in der Datenbank ablegt, hat dies den Vorteil, daß keine broken links auftreten können, weil ja die Bilder selbst genauso wie die Links auf die Bilder aus der Datenbank erzeugt werden. Liegen die Bilddaten dagegen im Dateisystem und die Datenbank enthält nur Pfadnamen, dann ist es problemlos möglich, daß jemand die Dateien umbenennt, ohne diese Änderung in der Datenbank nachzuführen und umgekehrt. Leider ist es speziell bei MySQL so, daß keinerlei Mechanismen vorhanden sind, die die referentielle Integrität der Datenbank sicherstellen, sodaß diese Sicherheit nicht wirklich gegeben ist. Dazu kommen noch eine Reihe von weiteren Nachteilen: * Wenn man die Bilddaten selbst in der Datenbank speichert, dann muß man für jedes Bild in einer Webseite ein Script starten. Das bedeutet, für eine Seite der Art _____________________________________________________________ index.php3:

Bla

_____________________________________________________________ muß nicht nur das Script index.php3 gestartet werden, um das HTML zu generieren, sondern für jedem Image-Tag auf der Seite muß ein Script sendimage.php3 gestartet werden, daß eine Datenbankverbindung aufmacht und das Bild aus der Datenbank fischt. Wenn CGI PHP verwendet wird, ist der Overhead noch viel größer, denn hier muß für jedes Bild ein 800 kB großer PHP-Prozeß erzeugt und gestartet werden. Legt man dagegen die Bilder als Dateien im Dateisystem ab, kann man mit der Static Page Engine des Webservers oder gar einem spezialisierten Bilder-Webserver arbeiten und ist um ca. den Faktor 10 effizienter. * MySQL kann BLOBs (binary large objects) nicht fragmentarisch bearbeiten, d.h. es ist nicht möglich, ein BLOB in kleinen Teilstücken aus der Datenbank zu holen oder den hinteren Teil eines BLOBs zu holen, ohne die Bytes davor zu lesen. Obendrein ist der Sendepuffer von MySQL für BLOBs begrenzt groß, sodaß nicht beliebig große BLOBs in der Datenbank abgelegt werden können. * Viele Datenbanken werden sehr ineffizient, wenn vergleichsweise große BLOBs zusammen mit anderen, sehr kleinen Objekten in derselben Tabelle gespeichert werden oder wenn eine Tabellenzeile mehr als ein BLOB enthält. Wie man Bilder in einer MySQL-Datenbank speichert, wird im Artikel [1551]Wie kann ich Bilder in einer MySQL-Datenbank speichern? beschrieben. 13.4. Windows: Jeder Zugriff auf meine Datenbank dauert eine halbe Minute! | [1552]SQL | [1553]Datenbank | [1554]Performance | [1555]Windows | [1556]langsam | Antwort von [1557]Kristian Köhntopp Eine Komponente Deines Netzes versucht, aus IP-Nummern per [1558]gethostbyaddr() auf den Hostnamen zu schließen und findet keinen Domain Name Server. Die Verarbeitung des Requests wird erst nach dem DNS-Timeout fortgesetzt. Sorge dafür, daß ein DNS-Server mit korrektem Reverse Lookup verfügbar ist oder sorge dafür, daß keine Reverse Lookups gemacht werden. Dazu mußt Du zunächst einmal die Komponente identifizieren, die die Lookups macht. 13.5. Wie kann ich meine Datenbankperformance steigern? | [1559]SQL | [1560]Datenbank | [1561]Performance | [1562]langsam | [1563]schneller | Antwort von [1564]Kristian Köhntopp Bei jeder Art von Performancetuning ist das wichtigste zunächst einmal eine Messung. Es kommt ganz entscheidend darauf an, als erstes festzustellen, was denn genau langsam ist, bevor man sich daran macht, die Dinge zu verändern. Wenn ein Script mit Datenbankzugriff zu langsam ist, dann kann dies an einer von mehreren Ursachen liegen. Die Datenbank steht offsite oder ist nur langsam erreichbar. Wenn die Datenbank nicht auf derselben Maschine läuft wie der Webserver, dann findet die Kommunikation zwischen Datenbank-Client und Server nicht mehr über schnelle Kommunikationsmethoden wie shared memory oder UNIX Domain Sockets statt, sondern über eine TCP/IP-Verbindung, die eine wesentlich geringere Kapazität und wesentlich höhere Latenzzeiten hat. Dies hat besonders fatale Auswirkungen, wenn die Datenbank und der Webserver durch eine langsames Netzwerk getrennt sind (Umlaufzeiten für Pakete von 10ms und mehr) oder wenn die Netzwerkbandbreite eingeschränkt ist (8 KB/sec und weniger). Hier kommt es ganz entscheidend darauf an, die Anzahl der Anfragen pro Seitenaufbau zu vermindern und die Menge der übertragenen Daten zu verringern. Die Anzahl der Abfragen läßt sich dadurch vermindern, daß man SQL JOIN-Operationen statt vieler Abfragen verwendet. Ein typisches, falsches Konstrukt ist _________________________________________________________________ # Liste der Treffer bestimmen $result=do_database_query("select id from tabelle where $bedingung"); # Treffer anzeigen reset($result); while(list($k, $v) = each($result)): $detail = do_detail_query("select * from tabelle2 where id =$v"); show_detail($detail); endwhile; _________________________________________________________________ Dieser Code generiert eine Masse von Queries nacheinander. Für jede einzelne Query wird ein Umlauf zur Datenbank und zurück notwendig und so summieren sich diese Umlaufzeiten zu gigiantischen Wartezeiten beim Seitenaufbau. Viel geschickter ist stattdessen _________________________________________________________________ # Treffer bestimmen $detail = do_database_query("select * from tabelle, tabelle2 where ( $bedingung ) and tabelle.id = tabelle2.id"); reset($detail); while(list($k, $v) = each($detail)): show_detail($v); endwhile; _________________________________________________________________ Dies liefert die gewünschten Daten mit einer einzigen Query. Die Datenbank hat hohe Verbindungsaufbaukosten und es wird CGI PHP verwendet. Einigen Datenbanken wie MySQL macht es nicht aus, Datenbanklinks zu öffnen und wieder zu schließen. Andere Datenbanken wie Oracle starten bei jedem Connect einen eigenen Clientprozeß. Dies ist ein sehr aufwendiger Vorgang. Wenn CGI PHP verwendet wird, dann ende der CGI Interpreter am Ende einer jeden Seite und mit dem Interpreter werden auch alle geöffneten Dateihandles und damit auch alle Datenbankverbindungen geschlossen - der Clientprozeß der Datenbank endet und muß für eine neue Seite neu geladen und gestartet werden. In solchen Fällen ist die Verwendung eines PHP-Interpreters als Modul angezeigter, weil in dieser Konfiguration die mit pconnect() geöffneten Links über die Lebensdauer einer PHP-Seite hinaus gehalten und auf neuen Seiten wiederverwendet werden können. Die Queries in der Datenbank sind nicht effizient. Alle Datenbanken haben Werkzeuge zur Analyse von Anfragen. In MySQL ist dies das EXPLAIN Kommando, in Oracle ist es EXPLAIN PLAN . Die Ausgabe dieser Kommandos sollte man in jedem Fall verstehen lernen und zu Rate ziehen. Nur so kann man erkennen, ob Indices zur Beschleunigung der Query verwendet werden, ob die Typen von Key und Foreign Key zueinander kompatibel sind und ob die Datenbank die richtige Tabelle als treibende Tabelle in einem JOIN verwendet. 13.6. Wie kann ich zwei Tabellen miteinander verknüpfen? | [1565]SQL | [1566]Datenbank | [1567]Tabelle | [1568]join | [1569]primary key | [1570]foreign key | Antwort von [1571]Kristian Köhntopp Man kann dies mit Hilfe einer JOIN-Operation tun. Diese ist im [1572]Kapitel 7.20 des [1573]MySQL-Handbuches beschrieben. Wenn die Tabellen artikel und email die Primärschlüsselfelder artikel.KundenID und email.eid haben und artikel mit email über den Fremdschlüssel email.KundenID verknüpft ist, dann kann man einen Equi-JOIN mit dem folgenden Statement formulieren: _________________________________________________________________ mysql> select * from artikel; +----------+ | KundenID | +----------+ | 1 | | 2 | | 3 | +----------+ 3 rows in set (0.00 sec) mysql> select * from email; +-----+----------+ | eid | KundenID | +-----+----------+ | 1 | 1 | | 2 | 2 | | 3 | 3 | +-----+----------+ 3 rows in set (0.00 sec) mysql> select a.KundenID as aid, > e.eid as eid, > e.KundenID as e_aid > from artikel as a, > email as e > where a.KundenID = e.KundenID; +-----+-----+-------+ | aid | eid | e_aid | +-----+-----+-------+ | 1 | 1 | 1 | | 2 | 2 | 2 | | 3 | 3 | 3 | +-----+-----+-------+ 3 rows in set (0.01 sec) _________________________________________________________________ In keinem Fall können in den herangejointen Tabellen Nullwerte enthalten sein. Diese Operation ist dann effizient, wenn a.KundenID und t.KundenID denselben Typ haben, und auf auf a.KundenID und t.KundenID ein UNIQUE INDEX oder ein INDEX liegen. In MySQL ist ein PRIMARY KEY immer auch ein UNIQUE INDEX. Wenn man optionale Werte hat, dann kann man keinen symmetrischen Join (Equijoin) mehr machen, sondern muß einen asymmetrischen Join (Left Join) durchführen. Dadurch können auf der rechten Seite Nullwerte entstehen: _________________________________________________________________ mysql> select * from telefon; +-----+----------+ | tid | KundenID | +-----+----------+ | 1 | 1 | | 2 | 3 | +-----+----------+ 2 rows in set (0.00 sec) Equijoin (es fehlt KundenID 2, weil keine Telefonnummer definiert ist): mysql> select a.KundenID as aid, > e.eid as eid, > e.KundenID as e_aid, > t.tid as tid, > t.KundenID as t_aid > from artikel as a, > email as e, > telefon as t > where a.KundenID = e.KundenID > and a.KundenID = t.KundenID; +-----+-----+-------+-----+-------+ | aid | eid | e_aid | tid | t_aid | +-----+-----+-------+-----+-------+ | 1 | 1 | 1 | 1 | 1 | | 3 | 3 | 3 | 2 | 3 | +-----+-----+-------+-----+-------+ 2 rows in set (0.02 sec) Left Join (generiert Nullwerte): mysql> select a.KundenID as aid, > t.tid as tid, > t.KundenID as t_aid > from artikel as a left join telefon as t > on a.KundenID = t.KundenID; +-----+------+-------+ | aid | tid | t_aid | +-----+------+-------+ | 1 | 1 | 1 | | 2 | NULL | NULL | | 3 | 2 | 3 | +-----+------+-------+ 3 rows in set (0.00 sec) Unterschiedliche Counts: mysql> select count(a.KundenID) as acount, > count(t.KundenID) as tcount > from artikel as a left join telefon as t > on a.KundenID = t.KundenID; +--------+--------+ | acount | tcount | +--------+--------+ | 3 | 2 | +--------+--------+ 1 row in set (0.01 sec) _________________________________________________________________ Die Tabelle a ist hier die aufspannende Tabelle, die Tabelle t ist die aufgespannte Tabelle. An den Stellen, an denen t keine zu a passenden Werte hat, tauchen Nullwerte in t auf. Da die Relation nun nicht mehr symmetrisch ist, muss man zwischen a.KundenID und t.KundenID unterscheiden. Insbesondere sind die count() -Werte beider Spalten unterschiedlich. Da a.KundenID und t.KundenID unterschiedlich sind, muß man auch zwingend mit qualifizierten Namen arbeiten und kann nicht mehr einfach KundenID schreiben. Ein gemischter Join verwendet Equijoins und Left Joins, wie es gerade paßt: _________________________________________________________________ mysql> select a.KundenID as aid, > e.eid as eid, e.KundenID as e_aid, > t.tid as tid, t.KundenID as t_aid > from artikel as a, > email as e left join telefon as t > on a.KundenID = t.KundenID > where a.KundenID = e.KundenID; +-----+-----+-------+------+-------+ | aid | eid | e_aid | tid | t_aid | +-----+-----+-------+------+-------+ | 1 | 1 | 1 | 1 | 1 | | 2 | 2 | 2 | NULL | NULL | | 3 | 3 | 3 | 2 | 3 | +-----+-----+-------+------+-------+ 3 rows in set (0.01 sec) _________________________________________________________________ 13.7. Was ist Aggregation? Was ist GROUP BY? | [1574]SQL | [1575]Datenbank | [1576]group by | Antwort von [1577]Kristian Köhntopp Mit Hilfe der GROUP BY -Clause kann man in SQL Daten aggregieren, also Äquivalenzklassen über den gefundenen Elementen bilden und mit den so gefundenen Teilmengen arbeiten. Gegeben sei eine Menge von Tupeln, etwa (1, 2) , (1, 3) , (2, 3) , (2, 2) , (3, 17) , (2, 21) . Man kann diese Menge jetzt in Teilmengen unterteilen, das wäre dann in der Mathematik eine Relation. Die Elemente, die gemeinsam in einer Teilmenge stehen, stehen dann in einer Relation zueinander . Eine Relation ist zum Beispiel kleiner als x . Nimmt man zum Beispiel die Menge |N und die Relation kleiner als 10 , dann teilt diese Relation die Menge |N in zwei Teilmengen, nämlich die Menge der natürlichen Zahlen, die die Relation erfüllen (also die Zahlen 1, 2, 3, ..., 9) und die Menge der natürlichen Zahlen, die die Relation nicht erfüllen (die Zahlen 10, ...). Ebenso kann man eine Äquivalenzrelation definieren. Eine solche Relation definiert mehrere Teilmengen und die Elemente einer Teilmenge sind gleich. In |N mit == als Relation ist das witzlos, da die Teilmengen dann einelementig sind, aber mit den o.a. Tupeln kann man ein sinnvolles Beispiel definieren, wenn man als Äquivalenzrelation Gleichheit des ersten Elementes definiert. Man bekommt dann die folgenden Teilmengen: _________________________________________________________________ Die Menge 1 == { (1, 2), (1, 3) } Die Menge 2 == { (2, 3), (2, 2), (2, 21) } Die Menge 3 == { (3, 17) } _________________________________________________________________ Angenommen, die Tupel seien eine Tabelle _________________________________________________________________ CREATE TABLE beispiel ( x integer, y integer ); _________________________________________________________________ dann würde man die o.a. Tupel als _________________________________________________________________ INSERT INTO beispiel (x, y) values (1, 2); INSERT INTO beispiel (x, y) values (1, 3); INSERT INTO beispiel (x, y) values (2, 3); INSERT INTO beispiel (x, y) values (2, 2); INSERT INTO beispiel (x, y) values (2, 21); INSERT INTO beispiel (x, y) values (3, 17); _________________________________________________________________ definieren und bekäme die Äquivalenzrelation aus dem Beispiel als _________________________________________________________________ SELECT x AS mengenname FROM beispiel GROUP BY x; +------------+ | mengenname | +------------+ | 1 | | 2 | | 3 | +------------+ 3 rows in set (0.01 sec) _________________________________________________________________ d.h. die Tupel (x, y) mit gleichem x bilden jeweils eine Menge. Wir sehen uns von diesen Tupeln jeweils nur die x an. Die Mächtigkeit der Mengen 1, 2 und 3 kann man mittels count() bestimmen: _________________________________________________________________ SELECT x AS mengenname, COUNT(x) AS maechtigkeit FROM beispiel GROUP BY x; +------------+--------------+ | mengenname | maechtigkeit | +------------+--------------+ | 1 | 2 | | 2 | 3 | | 3 | 1 | +------------+--------------+ 3 rows in set (0.00 sec) _________________________________________________________________ Man kann sich auch das Tupel (x, y) wieder ausgeben lassen: _________________________________________________________________ SELECT x, y FROM beispiel GROUP BY x; +------+------+ | x | y | +------+------+ | 1 | 2 | | 2 | 3 | | 3 | 17 | +------+------+ 3 rows in set (0.00 sec) _________________________________________________________________ MySQL wählt hier irgendein y , da ja per Definition alle (x, y) innerhalb einer Teilmenge gleich sind und jedes Element der Teilmenge daher als Repräsentant der Teilmenge gewählt werden kann. 13.8. Was ist der Unterschied zwischen connect und pconnect? | [1578]Persistenz | [1579]Datenbank | [1580]Verbindung | [1581]CGI | [1582]Modul | Antwort von [1583]Kristian Köhntopp In PHP bieten die meisten Datenbanken zwei connect() -Funktionen an: Eine gewöhnliche und eine pconnect() -Funktion. Verwendet man CGI PHP, unterscheiden sich beide Funktionen nicht. Verwendet man das PHP-Modul, werden die mit einem connect() hergestellten Datenbankverbindungen am Ende der Seite geschlossen. Mit pconnect() hergestellte Verbindungen bleiben jedoch geöffnet. Dies dient einzig und alleine dazu, das ständige Öffnen und Schließen von Netzwerkverbindungen zu vermeiden, denn der Verbindungsaufbau ist bei einigen Datenbanken (etwa Oracle) sehr aufwendig. Es ist daher empfehlenswert, in jedem Fall die pconnect() -Variante zu verwenden (aber: Vergleiche [1584]Webserver verstehen und tunen . Es können sehr viele offene Datenbankverbindungen entstehen). Siehe auch: PHP Manual, [1585]Persistente Datenbankverbindungen . 13.9. Wie kann ich mein Datenbankpaßwort gegen Spionage sichern? | [1586]SQL | [1587]Datenbank | [1588]Sicherheit | [1589]Passwort | Antwort von [1590]Kristian Köhntopp Viele PHP-Scripte enthalten Connectinformationen mit Paßworten und anderen wichtigen Daten, die nicht in falsche Hände fallen dürfen. Um diese Scripte gegen Zugriff von außen (über den Webserver) zu sichern, gibt es folgende Maßnahmen: * Die beste Lösung besteht darin, die Connectinformation in einer Include-Datei außerhalb des Webdateibaumes zu lagern. Wenn der eigene Webserver zum Beispiel die Document-Root /home/www/servers/www.kundenname.de/pages hat, man eigene Dateien jedoch schon ab der Ebene /home/www/servers/www.kundenname.de ablegen darf, dann ist es sinnvoll, sich ein Includeverzeichnis /home/www/servers/www.kundenname.de/php zu definieren und dort die Includedateien mit dem Loginnamen und Paßwort abzulegen. Da dieses Include-Verzeichnis außerhalb der Document-Root liegt und keine URL hat, kann es auch nicht über den Webserver abgerufen werden. * Die nächstschlechtere Lösung besteht darin, ein Verzeichnis unterhalb der Document-Root anzulegen und dieses Verzeichnis mit einem Paßwort zu sichern. Niemand, auch der Eigentümer des Servers, muß auf dieses Verzeichnis per HTTP zugreifen können, sondern Wartung erfolgt in der Regel per FTP - es kann also auch ein ungültiges Paßwort definiert werden. * Die schlechteste Lösung besteht darin, diese Include-Datein mit der Endung .php3 im normalen Dokumentenbaum zu hinterlegen und darauf zu vertrauen, daß Dateien mit dieser Endung immer geparsed werden. Während dies im Normalbetrieb immer der Fall ist, braucht der Webserver nur einmal ohne PHP-Modul gestartet zu werden und die Datei wird im Klartext ausgeliefert (näheres in " [1591]Wie kann ein Besucher meiner Seite den PHP-Code im Browser sehen? "). Die connect()-Funktionen in PHP verlangen alle, daß das Datenbankpaßwort im Klartext angegeben wird. Das bedeutet, daß das Paßwort entweder im PHP-Code im Klartext angegeben ist oder vom Code in Klartext entschlüsselt werden kann. Wenn jemand die Dateien mit den Paßworten oder dem Entschlüsselungscode lesen kann, dann bedeutet dies auch, daß das Klartextpaßwort dieser Person bekannt wird. Die betreffende Person braucht den Entschlüsselungscode nicht zu verstehen - sie braucht ihn nur auszuführen und er wird zwangsläufig das Klartextpaßwort passend für die Connect-Funktion liefern müssen. Daraus folgt, daß Schutz der Datenbankpaßworte nur durch Schutz der entsprechenden Quelltextdateien möglich ist. Es ist Aufgabe des Hosting-Environments beim Provider, diesen Schutz zu bieten, indem entweder Zugriffsrechte an den Dateien entsprechend gesetzt sind oder indem sogar eine virtuelle Dateiumgebung mit chroot() eingerichtet wird. 13.10. MySQL oder PostgreSQL? | [1592]SQL | [1593]Datenbank | [1594]MySQL | [1595]PostgreSQL | [1596]Vergleich | Antwort von [1597]Kristian Köhntopp von Kristian Köhntopp, Lutz Donnerhacke und Sebastian Bergmann. [1598]MySQL ist eine sehr populäre Datenbank, die sich vor allen Dingen durch Geschwindigkeit und geringen Speicherverbrauch sowie durch einfache Handhabung auszeichnet. MySQL verfügt über eine sehr gute [1599]Dokumentation , ist auch für die Windows-Plattform verfügbar und seit Ende Juni 2000 unter der GPL frei verfügbar. Seit Version 3.23.16 gibt es experimentellen Support für Transaktionen auf der Basis der [1600]Sleepycat DB3-Bibliothek, aber noch keine Trigger oder Rules. Das Buch [1601]MySQL von Paul DuBois ( [1602]englische Version ) erläutert die Datenbank umfassend und enthält eine Reihe von allgemeinen und speziell auf MySQL bezogenen Optimierungstips. Mit [1603]phpMyAdmin von Tobias Ratschiller existiert eine einfach zu bedienende, in PHP geschriebene Administrationsoberfläche für MySQL. PostgreSQL ist der großteils geglückte Versuch, eine freie Implementation von SQL92 aus einem SQL-fremden Konzept (Ingres) abzuleiten. Dazu gehören Transaktionen in verschiedenen Abschottungsgraden, Subselects, eigene Datentypen, Operatoren und Aggregatfunktionen, Trigger, Rules ('Trigger', die in die Optimierungsplanung eingehen) und Views. Es ist somit möglich, daß die Datenbank die Konsistenz des Datenbestandes aus sich heraus erzwingt und so Direktzugriffe ohne korrigierende Frontends gestattet. Die Geschwindigkeit von PostgreSQL ist dadurch allerdings vermindert. Verzichtet man auf Datanbankkonsistenz auf der Platte im Falle von OS-Abstürzen, so wird PostgreSQL deutlich schneller. Ebenso wie MySQL fehlen auch PostgreSQL noch einige elementare Funktionen zur vollen SQL92-Kompatibilität, im Falle von PostgreSQL sind dies zum Beispiel Outer Joins. Die Möglichkeiten von PostgreSQL machen es notwendig, die Datenbank vorab sorgfältig zu planen. Inzwischen existiert mit [1604]phpPgAdmin eine von Dan Wilson nach PostgreSQL portierte Version von phpMyAdmin. 13.11. Wie komme ich bei meinem Provider an die Datenbank? | [1605]Datenbank | [1606]Passwort | [1607]Zugangsdaten | [1608]Puretec | [1609]Strato | Antwort von [1610]Kristian Köhntopp Solche Fragen klärt man am Besten mit dem Telefonsupport des betreffenden Providers. * Puretec 1&1: _____________________________________________________________ mysql_connect("dbxy.puretec.de", "Nummer", "Passwort"); _____________________________________________________________ Dabei steht xy für die Nummer des Datenbankservers, die im 1&1-Online-Konfigurationsmenü unter "Zugangsdaten -> Datenbank-Zugriff -> Host" zu finden ist. 13.12. Wie kann ich auf einen ODBC-Server (MSSQL, Access) zugreifen? | [1611]SQL | [1612]Datenbank | [1613]ODBC | [1614]Access | [1615]Windows | Antwort von [1616]Kristian Köhntopp In Windows kann man einfach den mitgelieferten ODBC-Treiber verwenden. Eine Beschreibung in englischer Sprache befindet sich [1617]im englischen PHP-Manual . In Unix kann man für den Zugriff auf einen Microsoft SQL Server den Sybase-CT Treiber verwenden, der ein weitgehend kompatibles Protokoll verwendet. Sybase bietet eine frei verfügbare Version der benötigten Bibliotheken für Linux zum [1618]download an. Alternativ kann man auch einen kommerziellen ODBC-Treiber für Unix verwenden, etwa den Treiber von [1619]OpenLink Software oder den iODBC-Treiber, der dem Adabas-Paket für Suse Linux beiliegt. 13.13. Wieso wird aus " plötzlich \" und wie geht das wieder weg? | [1620]escape | [1621]backslash | [1622]quote | [1623]magic_quotes | Antwort von [1624]Johannes Frömter Das sind Escapes, die vor bestimmten Sonderzeichen stehen, um diese zu "entschärfen". Verantwortlich für dieses Verhalten ist die Funktion magic_quotes von PHP, die üblicherweise in der php.ini eingestellt wird; dabei gilt [1625]magic_quotes_gpc für Daten, die per GET, POST oder COOKIE übergeben werden und [1626]magic_quotes_runtime für Daten, die aus Datenbanken, Dateien oder anderen externen Quellen kommen. Escaped werden ' (single quote), " (double quote), \ (backslash) und NUL (das Null-Byte). Um die Escape-Zeichen wieder zu entfernen, benutzt man [1627]stripslashes() , manuell hinzufügen kann man sie mittels [1628]addslashes() . Die Konfiguration von magic_quotes kann man an verschiedenen Stellen beeinflussen: _________________________________________________________________ php.ini: magic_quotes_runtime = on|off magic_quotes_gpc = on|off .htaccess, httpd.conf: php_flag magic_quotes_runtime on|off php_flag magic_quotes_gpc on|off .php ini_set("magic_quotes_runtime", 0|1); // magic_quotes_gpc geht hier nicht _________________________________________________________________ In Verbindung mit Sybase-Datenbanken (d.h. bei zusätzlich gesetzter Option [1629]magic_quotes_sybase ) gibt es eine Besonderheit: hier werden single quotes nicht mit einem Backslash, sondern mit einem weiteren single quote escaped. 13.14. Warum soll ich nicht SELECT * schreiben? | [1630]SQL | [1631]Select | [1632]Wildcard | [1633]Performance | Antwort von [1634]Johannes Frömter Bei der SQL-Anweisung SELECT * FROM ... muß das Datenbank-Management-System (DBMS) alle Spalten der betreffenden Datensätze selektieren, auch wenn in der anschließenden Verarbeitung nur ein Teil davon wirklich gebraucht wird. Das ist langsam und schlicht und einfach unsinnig. Selbst wenn alle Spalten tatsächlich benötigt werden, sollten sie separat aufgeführt werden, weil * die Tabelle nachträglich erweitert werden könnte, die neue(n) Spalte(n) (im worst case ein BLOB!) nach der Abfrage aber nicht gebraucht werden * die Reihenfolge der Spalten bei der Ausgabe sonst undefiniert ist (bei den meisten Datenbanken ist es die Reihenfolge der Spaltendefinition bei der Anlage der Tabelle). Diese Reihenfolge könnte sich ändern (z.B. durch Einspielen eines Backups nach Erweiterung der Tabelle, durch eine neue Version des DBMS, etc.) * die Spalten sonst möglicherweise keinen vernünftigen oder eindeutigen Namen haben - führt man die Spalten einzeln an, kann man mittels AS einen Namen (Alias) vergeben: SELECT p.pers_p_nr AS personalnummer FROM personal p Im [1635]MySQL-Manual wird in den Beispielen der Einfachheit halber fast immer SELECT * verwendet. Daran darf man sich jedoch für die Praxis kein Beispiel nehmen, sagt auch das Handbuch explizit - Zitat: You should NEVER, in an application, use SELECT * and retrieve the columns based on their position, because the order in which columns are returned CANNOT be guaranteed over time; A simple change to your database may cause your application to fail rather dramatically. Auch für INSERT gilt: immer alle Spaltennamen angeben! Statt INSERT INTO tabelle VALUES (1, 2, 3) ist also INSERT INTO tabelle (spalte1, spalte2, spalte3) VALUES (1, 2, 3) zu schreiben. 14. Datenbanken: MySQL 14.1. [1636]Kommt MySQL mit mehr als x Datensätzen pro Tabelle klar? Wie stabil ist MySQL? 14.2. [1637]Wie greife ich auf eine MySQL-Datenbank zu? 14.3. [1638]Mein Script verbraucht so viel Speicher beim Datenbankzugriff 14.4. [1639]Wie kann ich eine CSV-Datei in MySQL importieren? 14.5. [1640]Wie kann ich eine CSV-Datei aus MySQL exportieren? 14.6. [1641]Wie kann ich die Datensätze der letzten 2 Wochen listen? 14.7. [1642]Wie kann ich eine Tabelle nach IP-Nummern sortieren lassen? 14.8. [1643]Wie lösche ich alle Datensätze, die älter als n Tage sind? 14.9. [1644]Wie kann ich Bilder in einer MySQL-Datenbank speichern? 14.10. [1645]Wie kann ich einen zufälligen Eintrag aus einer MySQL-Tabelle auswählen? 14.11. [1646]Ich habe eine Tabelle mit n Einträgen und möchte auf jeder Seite m davon anzeigen 14.12. [1647]Wozu ist auto_increment nützlich? Wie erfahre ich den Wert des letzten Inkrements? 14.13. [1648]Wie lege ich den Initialwert des auto_increment fest? Läuft dieser Wert über? 14.14. [1649]Wie realisiere ich eine Volltextsuche mit MySQL? 14.15. [1650]Meine Datenbankabfrage/Mein SQL-Statement funktioniert nicht 14.16. [1651]Wie kann ich Umlaute richtig sortieren? 14.1. Kommt MySQL mit mehr als x Datensätzen pro Tabelle klar? Wie stabil ist MySQL? | [1652]SQL | [1653]Datenbank | [1654]MySQL | [1655]Tabelle | [1656]Tabellengroesse | [1657]Stabilitaet | [1658]Groesse | Antwort von [1659]Kristian Köhntopp MySQL ist als Datenbank äußerst stabil und auch bei großen Datenmengen extrem schnell und effizient. Die Grenzen von MySQL liegen nicht so sehr in der Größe der Tabellen oder der Anzahl von Datensätzen, sondern in der Komplexität der Datenmodelle, die damit implementiert werden kann. MySQL speichert Daten mit Index in Baumstrukturen. Auf diese Datenstrukturen kann mit logarithmischer Komplexität zugegriffen werden, d.h. für eine Tabelle mit n Datensätzen sind log(b, n) Zugriffe notwendig, bis der gesuchte Datensatz gefunden ist. b ist die Basis des Logarithmus. Wäre b gleich 2, dann wären zum Zugriff auf eine Tabelle mit 1.000 Datensätzen maximal 10, auf eine Tabelle mit 1.000.000 Datensätzen maximal 20 und auf eine Tabelle mit 1.000.000.000 maximal 30 Zugriffe notwendig, um einen beliebigen Zieldatensatz über den Index zu finden. Tatsächlich ist die Basis jedoch nicht 2, sondern weit größer. Sie ist abhängig von der internen Blockgröße der Datenbank und der mittleren Satzlänge in einem Index. Man kann annehmen, daß sie je nach Art der Daten zwischen 20 und 40 liegt. Damit wären zum Finden eines Datensatzes aus 1.000 Datensätzen maximal 3, aus 1.000.000 Datensätzen maximal 5 und aus 1.000.000.000 Datensätzen maximal 7 Vergleiche und Plattenzugriffe notwendig. Entsprechend sind die Erfahrungen, die mit MySQL berichtet werden: Im Rahmen der Begrenzungen des Betriebssystems (maximale Dateigröße 2 GB?) kommt MySQL mit beliebig großen Tabellen problemlos klar. Beschränkungen ergeben sich in MySQL aus dem Fehlen bestimmter Eigenschaften wie Erzwingung referentieller Integrität (keine foreign key Prüfungen, siehe dazu auch die Bemerkungen über PostgreSQL) und Transaktionen. Das Fehlen dieser Eigenschaften macht die Implementierung von Datenbankschemata sehr mühsam, die schreibend auf mehr als eine Tabelle zur Zeit zugreifen. Man kann abschätzen, ob MySQL für eine Aufgabe das passende Tool ist, indem man sich das geplante Datenbankschema und die geplanten Transaktionen auf diesem Schema ansieht, alle n:m und Sternbeziehungen isoliert und dann feststellt, in welchen dieser Beziehungen schreibende Zugriffe notwendig sind, die mehr als eine Tabelle aktualisieren. MySQL ist geeignet für alle Modelle, die read-mostly sind oder die weitaus überwiegend Schreibzugriffe auf einzelne Tabellen haben. MySQL ist nicht optimal geeignet, wenn ein Modell sehr viele Schreibzugriffe hat, wenn ein Modell mehr als 2 Schreibzugriffe hat, die mehr als eine Tabelle gleichzeitig aktualisieren oder wenn ein Modell zwingend auf referentielle Integrität angewiesen ist, aber mehr als eine Anwendung schreibend auf den Bestand zugreift. 14.2. Wie greife ich auf eine MySQL-Datenbank zu? | [1660]SQL | [1661]Datenbank | [1662]MySQL | [1663]Zugriff | [1664]connect | [1665]mysql_connect | [1666]pconnect | [1667]mysql_pconnect | [1668]mysql_fetch_row | [1669]mysql_fetch_array | [1670]mysql_query | [1671]mysql_num_rows | Antwort von [1672]Kristian Köhntopp Mit Hilfe der Funktion [1673]mysql_connect() kann man eine Datenbankverbindung zu einem MySQL-Server aufbauen, um dann mit [1674]mysql_select_db() die Datenbank auf dem Server auszuwählen. Das Resultat ist eine Link-ID , die man bei allen anderen MySQL-Funktionen angeben kann. In der php3.ini kann man festlegen, welche Datenbank und welcher Benutzername und welches Paßwort die Funktion verwenden soll, wenn diese Angaben beim Funktionsaufruf weggelassen werden, auch wenn es nur untermittelschlau ist, dies zu tun: _________________________________________________________________ ; default host for mysql_connect() (doesn't apply in safe mode) mysql.default_host = ; default user for mysql_connect() (doesn't apply in safe mode) mysql.default_user = ; default password for mysql_connect() (doesn't apply in safe mode) mysql.default_password = ; Note that this is generally a *bad* idea to store passwords ; in this file. *Any* user with PHP access can run ; 'echo cfg_get_var("mysql.default_password")' and reveal that ; password! And of course, any users with read access to this ; file will be able to reveal the password as well. _________________________________________________________________ Kontaktaufnahme mit dem Server und Festlegen der Datenbank: _________________________________________________________________ $link = mysql_connect("localhost","ich","geheim"); if (!$link) die("Kann den Server nicht erreichen."); if (!mysql_select_db("meinedaten", $link)) die("Kann die Datenbank nicht anwählen."); _________________________________________________________________ Das Senden einer Anfrage an die Datenbank erfolgt mit Hilfe der Funktion [1675]mysql_query() . Diese Funktion liefert einen Result-Identifier, mit dem dann das Ergebnis abgefragt werden kann: Mit der Funktion [1676]mysql_num_rows() bestimmt man die Anzahl der Zeilen des Ergebnissis, mit Hilfe der Funktion [1677]mysql_fetch_array() kann man die jeweils aktuelle Zeile des Ergebnisses einlesen. _________________________________________________________________ $query = sprintf("select * from meinetabelle order by id"); $result = mysql_query($query, $link); if (!$result) { print mysql_error(); die("Query $query ist ungültiges SQL."); } $zeilen = mysql_num_rows($result); printf("Das Ergebnis hat %d Zeilen.\n", $zeilen); while($avar = mysql_fetch_array($result)) printf("Spalte bla hat den Wert %s\n", $avar["bla"]); mysql_free_result($result); mysql_close($link); _________________________________________________________________ 14.3. Mein Script verbraucht so viel Speicher beim Datenbankzugriff | [1678]SQL | [1679]Datenbank | [1680]MySQL | [1681]Speicher | [1682]Speicherverbrauch | [1683]RAM | [1684]mysql_free_result | Antwort von [1685]Kristian Köhntopp Wenn ein PHP3-Script so geschrieben ist, daß es in einer Schleife SELECT-Anfragen stellt, ohne [1686]mysql_free_result() auf die Result-Sets aufzurufen, dann wird es immer mehr Speicher verbrauchen. _________________________________________________________________ while(irgendwas) { $res = mysql_query("SELECT wunderquery"); machwas_und_generiere_insert_oder_update(); // mysql_free_result($res); } _________________________________________________________________ In diesem Fall wird bei jedem Schleifendurchlauf für das SELECT-Ergebnis neuer Speicher bestellt und dieser niemals wieder freigeben. Der PHP-Prozess wird über alle Grenzen wachsen. Entfernt man die beiden Kommentarzeichen in der Schleife, wird dies nicht passieren. In PHP4 gibt das Reference Counting den Speicher automatisch frei. 14.4. Wie kann ich eine CSV-Datei in MySQL importieren? | [1687]SQL | [1688]Datenbank | [1689]MySQL | [1690]CSV | [1691]Import | [1692]infile | Antwort von [1693]Kristian Köhntopp In MySQL gibt es die Anweisung LOAD DATA INFILE zum Importieren von Dateien im CSV-Format in die Datenbank. Diese Anweisung wird vom Datenbankserver ausgeführt, d.h. die Datei muß auf dem Rechner liegen, auf dem der Datenbankserver abläuft und die Datei muß word-readable sein. Man benötigt file_priv , um dieses Kommando ausführen zu können. Seit Version 3.22.6 von MySQL gibt es die Kommandovariante LOAD DATA LOCAL INFILE zum Importieren von Daten im CSV-Format. Dieses Kommando wird auf dem MySQL-Client (also im PHP-Interpreter) ausgeführt. Die Datei muß also auf dem Rechner liegen, auf dem der Client läuft und durch den Client lesbar sein. Man benötigt keine besonderen Privilegien, um dieses Kommando ausführen zu können. Dies ist die empfohlene Variante des Kommandos, falls die zur Verfügung steht. Das folgende SQL-Kommando liest eine Datei ein, bei der die Datensätze optional mit Anführungszeichen eingeschlossen sind und durch Semikolons getrennt sind. Vorhandene Datensätze in der Tabelle, die ebenfalls im Import enthalten sind, werden durch den Import überschrieben. _________________________________________________________________ LOAD DATA LOCAL INFILE '/home/www/servers/www.servername.de/tmp/import.csv' REPLACE INTO TABLE tabellenname FIELDS TERMINATED BY ';' OPTIONALLY ENCLOSED BY '"'; _________________________________________________________________ Eine vollständige Beschreibung des Kommandos in englischer Sprache ist Bestandteil des MySQL Manuals unter der URL [1694]http://www.mysql.com/Manual/manual.html#LOAD_DATA . Will man die Daten manuell laden, darf man die Zeile nicht mit [1695]explode() zerlegen, weil dies bei Datensätzen versagt, die selbst Kommata enthalten. Stattdessen bietet PHP die Funktion [1696]fgetcsv() an. 14.5. Wie kann ich eine CSV-Datei aus MySQL exportieren? | [1697]SQL | [1698]Datenbank | [1699]MySQL | [1700]CSV | [1701]Export | [1702]outfile | Antwort von [1703]Kristian Köhntopp Die Umkehrung von LOAD DATA INFILE ist das SELECT INTO OUTFILE , eine Variante des regulären SELECT. _________________________________________________________________ SELECT INTO OUTFILE '/home/www/servers/www.servername.de/tmp/export.csv' FIELDS TERMINATED BY ';' OPTIONALLY ENCLOSED BY '"' FROM ...; _________________________________________________________________ Das Kommando wird die Datei auf dem Rechner anlegen, auf dem der Datenbankserver läuft und die Datei wird dem Benutzer gehören, unter dessen User-ID der Datenbankserver abläuft. Der Datenbankserver wird eine existierende Datei nicht überschreiben. Zur Ausführung des Kommandos ist file_priv notwendig. Eine vollständige Beschreibung des Kommandos in englischer Sprache ist Bestandteil des MySQL Manuals unter der URL [1704]http:/www.mysql.com/Manual/manual.html#SELECT . Wenn man kein file_priv hat, muß man sich stattdessen eine entsprechende Funktion in PHP selber bauen. Dabei ist folgendes zu beachten: * In CSV-Dateien sind Datensätze durch Kommata getrennt. * In CSV-Dateien sind Datensätze, die Sonderzeichen enthalten, insbesondere solche, die Kommata oder Anführungszeichen enthalten, durch Anführungszeichen einzuschließen. * In CSV-Dateien dürfen alle Datensätze in Anführungszeichen eingeschlossen werden. * In CSV-Dateien sind in Datensätzen, die Anführungszeichen enthalten, die Anführungszeichen zu verdoppeln. Eine zweispaltige Tabelle, die die Tupel ( a; 10,4) und (b; Er sagte: "Hallo, Du!" ) enthält, muß nach dem Export also so aussehen: _________________________________________________________________ a,"10,4" b,"Er sagte: ""Hallo, Du!""" _________________________________________________________________ 14.6. Wie kann ich die Datensätze der letzten 2 Wochen listen? | [1705]SQL | [1706]Datenbank | [1707]MySQL | [1708]timestamp | [1709]aktuell | Antwort von [1710]Kristian Köhntopp Ein vollständiges Beispiel folgt. Zunächst die Definition einer einfachen Tabelle mit einigen Werten: _________________________________________________________________ mysql> create table beispiel ( -> id integer not null auto_increment primary key, -> nutzlast varchar(80) not null, -> changed timestamp not null ); Query OK, 0 rows affected (0.00 sec) mysql> insert into beispiel ( nutzlast ) values ( "eins"); Query OK, 1 row affected (0.02 sec) mysql> insert into beispiel ( nutzlast ) values ( "zwei"); Query OK, 1 row affected (0.00 sec) mysql> insert into beispiel ( nutzlast ) values ( "drei"); Query OK, 1 row affected (0.00 sec) mysql> select * from beispiel; +----+----------+----------------+ | id | nutzlast | changed | +----+----------+----------------+ | 1 | eins | 20000126204514 | | 2 | zwei | 20000126204517 | | 3 | drei | 20000126204520 | +----+----------+----------------+ 3 rows in set (0.02 sec) mysql> update beispiel set changed = "20000101123456" where id = 2; Query OK, 1 row affected (0.01 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> select * from beispiel where changed > "20000115000000"; +----+----------+----------------+ | id | nutzlast | changed | +----+----------+----------------+ | 1 | eins | 20000126204514 | | 3 | drei | 20000126204520 | +----+----------+----------------+ 2 rows in set (0.02 sec) mysql> quit Bye _________________________________________________________________ In diesem Beispiel ist eine Tabelle beispiel angelegt worden, die neben einer Nutzlastspalte mit dem Namen nutzlast auch noch einen automatisch vergebenen Primärschlüssel id und ein automatisch aktualisiertes Änderungsdatum enthält. Wir haben das Änderungsdatum für den Datensatz mit der Nummer 2 künstlich zurückgestellt. Das folgende Beispielscript listet nun alle Datensätze, die in den letzten 2 Wochen geändert worden sind. Es verwendet die Klasse DB_Sql aus [1711]PHPLIB zum Zugriff auf die Datenbank, aber die Umstellung auf die Verwendung direkter Datenbankfunktionen ist trivial. _________________________________________________________________ kris@valiant:~/Source/php3 > cat /tmp/probe.php3 #! /home/kris/Source/php3/php -q '%s'", $twoweeks); printf("query = %s\n", $query); # Query senden und Resultat einlesen. $db = new DB_Bla; $db->query($query); while ($db->next_record()) { printf("%s %s %s\n", $db->f("id"), $db->f("nutzlast"), $db->f("changed")); } ?> kris@valiant:~/Source/php3 > /tmp/probe.php3 query = select id, nutzlast, date_format(changed, '%Y-%m-%d %H:%i:%s') as changed from beispiel where changed > '20000112210135' 1 eins 2000-01-26 20:45:14 3 drei 2000-01-26 20:45:20 _________________________________________________________________ 14.7. Wie kann ich eine Tabelle nach IP-Nummern sortieren lassen? | [1712]SQL | [1713]Datenbank | [1714]MySQL | [1715]IP-Nummer | [1716]sortieren | Antwort von [1717]Kristian Köhntopp IP-Nummern sind 32-Bit Zahlen. Die natürliche Sortierreihenfolge ergibt sich, wenn man die IP-Nummer in eine Zahl umrechnet und dann nach dieser Zahl sortiert. Also _________________________________________________________________ 0.0.0.1 -> 1 0.0.1.0 -> 256 a.b.c.d ipnr = a * 256*256*256 + b * 256*256 + c * 256 + d _________________________________________________________________ Die Aufgabe besteht also darin, einen SELECT-Ausdruck zu finden, sodaß man _________________________________________________________________ select ip, ... as a, ... as b, ... as c, ... as d from iptest; +----------------+------+------+------+------+ | ip | a | b | c | d | +----------------+------+------+------+------+ | 1.2.3.4 | 1 | 2 | 3 | 4 | | 10.11.12.13 | 10 | 11 | 12 | 13 | | 100.101.102.10 | 100 | 101 | 102 | 10 | | 111.11.1.0 | 111 | 11 | 1 | 0 | | 111.10.3.10 | 111 | 10 | 3 | 10 | | 193.98.110.1 | 193 | 98 | 110 | 1 | | 193.174.3.10 | 193 | 174 | 3 | 10 | +----------------+------+------+------+------+ 7 rows in set (0.00 sec) _________________________________________________________________ ausrechnen kann. Hat man das, kann man sehr leicht ein _________________________________________________________________ update iptest set ipnr= a * 256*256*256 + b * 256*256 + c * 256 + d; _________________________________________________________________ generieren und dann mit _________________________________________________________________ select * from iptest order by ipnr; _________________________________________________________________ die gewünschte Ausgabe generieren. Bleibt das Problem, geeignete Ausdrücke für die Platzhalter ... zu finden. Man kann nicht mit substring() alleine arbeiten, da durch Vorbedingungen bei der Eingabe sowohl 001.002.003.004 als auch 1.2.3.4 legal ist. Es ist also notwendig, mittels locate() die Positionen der Punkte zu bestimmen und dann mit variablen Positionen zu arbeiten. Die folgende, vergleichsweise elegante Lösung stammt von Martin Ramsch: _________________________________________________________________ SELECT ip, (( FLOOR(ip)*256 +FLOOR(SUBSTRING_INDEX(ip,'.',-3)) ) *256 +FLOOR(SUBSTRING_INDEX(ip,'.',-2)) )*256 +FLOOR(SUBSTRING_INDEX(ip,'.',-1)) AS ipnr FROM iptest ORDER BY ipnr; _________________________________________________________________ Dabei spielt man damit, daß der String 1.2.3 im numerischen Kontext einfach als die Zahl 1.2 interpretiert wird. Sauberer wäre es, die Textteile hinter dem Komma immer noch wegzulassen - aber dann wird die Lösung monströser: _________________________________________________________________ SELECT ip, (( SUBSTRING_INDEX(ip,'.',1) *256 +SUBSTRING_INDEX(SUBSTRING_INDEX(ip,'.',-3),'.',1) ) *256 +SUBSTRING_INDEX(SUBSTRING_INDEX(ip,'.',-2),'.',1) )*256 +SUBSTRING_INDEX(SUBSTRING_INDEX(ip,'.',-1),'.',1) AS ipnr FROM iptest ORDER BY ipnr; _________________________________________________________________ Antwort von [1718]Johannes Frömter Ab MySQL 3.23.15 gibt es die [1719]Funktionen INET_NTOA(expr) (number to address) und INET_ATON(expr) (address to number), die einem die Konvertierung abnehmen. Damit ist das Sortieren von IP-Adressen kein Problem mehr: _________________________________________________________________ SELECT ip FROM table ORDER BY INET_ATON(ip); _________________________________________________________________ 14.8. Wie lösche ich alle Datensätze, die älter als n Tage sind? | [1720]SQL | [1721]Datenbank | [1722]MySQL | [1723]timestamp | [1724]loeschen | [1725]aufraeumen | Antwort von [1726]Kristian Köhntopp Die betreffende Tabelle sollte ein Datumsfeld haben, etwa ein selbstaktualisierendes Feld vom Typ TIMESTAMP oder ein manuell aktualisiertes Feld vom Typ DATE . Die folgenden drei Queries löschen jeweils alle Datensätze, die älter als 30 Tage sind, mit steigender Effizienz. _________________________________________________________________ 1. delete from kalender as k where (to_days(current_date) - to_days(k.datum)) > 30 2. delete from kalender as k where to_days(k.datum) < to_days(current_date)-30; 3. delete from kalender as k where k.datum < date_add(current_date, interval -30 day) _________________________________________________________________ Die erste Query ist vergleichsweise langsam, denn hier ist die linke Seite der Query ein Ausdruck, der für jede Zeile berechnet werden muß. Der Spaltenname k.datum taucht auf der linken Seite in einer Funktionsanwendung auf, sodaß keine Indices angewendet werden können. Die zweite Query ist insofern optimiert, als daß der konstante Teil der Rechnung auf die rechte Zeit gebracht werden kann, sodaß diese Seite der Ungleichung zu einer Konstanten optimiert werden kann. Die linke Seite der Query ist jedoch noch immer eine Funktionsanwendung, sodaß ein full table scan notwendig ist. Die dritte Query ist durchoptimiert: Hier ist die linke Seite der Ungleichung ein reiner Spaltenausdruck, die rechte Seite zu einer Konstanten optimierbar. Wenn ein INDEX(k.datum) existiert, kann er in dieser Query angewendet werden, um den Zugriff zu beschleunigen. 14.9. Wie kann ich Bilder in einer MySQL-Datenbank speichern? | [1727]SQL | [1728]Datenbank | [1729]MySQL | [1730]Bild | [1731]Download | [1732]Upload | [1733]Performance | [1734]BLOB | Antwort von [1735]Martin Jansen Die Vor- und Nachteile dieser Methode werden im Kapitel "Datenbanken" im Abschnitt " [1736]Ist es sinnvoll, Bilder in einer Datenbank abzulegen? " diskutiert. Eine sehr gelungene Anleitung, um Binärdaten (also auch Bilder) in einer MySQL-Datenbank zu speichern, beschreibt Florian Dittmer auf [1737]http://www.phpbuilder.com/columns/florian19991014.php3 . In einem [1738]Artikel bei ZDnet beschreibt Julie Meloni einen Datei-Upload per Formular in die Datenbank inkl. Hinweisen speziell zu MySQL, Microsoft SQL Server und Oracle. 14.10. Wie kann ich einen zufälligen Eintrag aus einer MySQL-Tabelle auswählen? | [1739]SQL | [1740]Datenbank | [1741]MySQL | [1742]Zufall | [1743]random | Antwort von [1744]Martin Jansen Seit MySQL 3.23 steht folgende Syntax zur Verfügung, um einen zufälligen Eintrag aus einer Datenbank-Tabelle auszulesen: _________________________________________________________________ SELECT * FROM tabelle ORDER BY RAND() LIMIT 1 _________________________________________________________________ Antwort von [1745]Martin Jansen Falls die MySQL-Version, die zum Einsatz kommt, älter als 3.23 ist, kann man alternativ folgendes Script verwenden, um einen zufälligen Datensatz auszulesen: _________________________________________________________________ _________________________________________________________________ 14.11. Ich habe eine Tabelle mit n Einträgen und möchte auf jeder Seite m davon anzeigen | [1746]SQL | [1747]Datenbank | [1748]MySQL | [1749]blaettern | [1750]vor | [1751]zurueck | [1752]limit | Antwort von [1753]Kristian Köhntopp In MySQL kann man zu diesem Zweck die LIMIT -Direktive verwenden, die m Einträge ab Position s einer geordneten Tabelle anzeigt. In anderen Datenbanken muß man sich eine Zeilennummer definieren und kann dann einen Teil der Tabelle mittels einer BETWEEN -Clause auswählen. _________________________________________________________________ # MySQL mysql> select * from tabelle limit s,m; _________________________________________________________________ Es ist nicht effizient, alle n Datensätze der Tabelle zu selektieren und dann alle Datensätze vor Position s zu überlesen. Antwort von [1754]Daniel T. Gorski Mit Hilfe eines solchen SQL-Statements kann man sich dann leicht eine Funktion schreiben, die den entsprechenden Ausschnitt der Tabelle anzeigt und Links zum vorhergehenden und folgenden Tabellenausschnitt enthält. Im Folgenden möchten wir hier diese einfache "Blättern"-Funktion realisieren, die uns erlaubt, über die Ergebnisse einer Datenbank-Query vor- und zurück zu browsen. Es wird davon ausgegangen, daß eine einfache MySQL-Datenbank (deren Name über die Variable $database definiert wird) vorhanden ist. Diese enthält die von uns benötigte Tabelle (Variable $table ), über die wir "blättern" wollen. _________________________________________________________________ CREATE DATABASE nameDerDatenbank; USE nameDerDatenbank; CREATE TABLE nameDerTabelle (ID int(10) unsigned NOT NULL, INHALT text NOT NULL); _________________________________________________________________ Diese Datenbankstruktur kann z.B. mit [1755]phpMyAdmin , einem anderem PHP-Script oder direkt mit dem MySQL-Monitor erstellt werden. Um Ausgabeergebnisse zu erhalten, muß die Tabelle selbstverständlich zuerst mit Inhalt gefüllt werden - an dieser Stelle gehen wir davon aus, daß die Tabelle mehrere Einträge enthält. _________________________________________________________________
MySQL sagt: ".mysql_error()); // Feststellen der Anzahl der verfügbaren Datensätze. $resultID = @mysql_query("SELECT COUNT(ID) FROM ".$table); $total = @mysql_result($resultID,0); // Ggf. $start korrigieren (falls Parameter in // der URL manipuliert wurde) $start = ($start >= $total) ? $total - $limit : $start; // Datenbankabfrage ausführen. $query = "SELECT ID,INHALT FROM ".$table ." LIMIT ".$start.",".$limit; $resultID = @mysql_query($query); // Ergebnisse lesen und an den Client ausgeben while ($data = mysql_fetch_array($resultID)) { echo $data["ID"].": ".$data["INHALT"]."
"; } // Zurück- und Vorblättern if ($start > 0) { $newStart = ($start - $limit < 0) ? 0 : ($start-$limit); echo "<< zurück"; } if ($start + $limit < $total) { $newStart = $start + $limit; echo " vor >>"; } // Die benutzte (nichtpersistente) Verbindung zu der MySQL-Datenbank, // wird nach dem Script-Ende automatisch geschlossen. // That's it. ?> _________________________________________________________________ 14.12. Wozu ist auto_increment nützlich? Wie erfahre ich den Wert des letzten Inkrements? | [1756]SQL | [1757]Datenbank | [1758]MySQL | [1759]ID | [1760]auto_increment | [1761]Nummer | Antwort von [1762]Daniel T. Gorski Ganzzahlige Datenbankfelder in MySQL können mit dem Attribut auto_increment versehen werden. Wird über die betreffende Tabelle eine INSERT-Query ausgeführt, so wird automatisch der Wert des mit auto_increment gekennzeichneten Feldes um Eins erhöht (inkrementiert), ohne daß dieses in der INSERT-Query explizit angegeben werden muß bzw. darf. Dies ist bei "flachen" Tabellen ohne Relationen nützlich z.B. bei Gästebüchern. Mit dem MySQL-Monitor erzeugtes Beispiel: _________________________________________________________________ mysql> CREATE DATABASE foo; mysql> USE foo; mysql> CREATE TABLE bar ( -> ID int(10) unsigned NOT NULL auto_increment, -> INHALT varchar(32) NOT NULL, -> PRIMARY KEY (ID) -> ); mysql> DESCRIBE bar; +--------+------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +--------+------------------+------+-----+---------+----------------+ | ID | int(10) unsigned | | PRI | 0 | auto_increment | | INHALT | varchar(32) | | | | | +--------+------------------+------+-----+---------+----------------+ mysql> INSERT bar SET INHALT='erster Datensatz'; mysql> INSERT bar SET INHALT='zweiter Datensatz'; mysql> SELECT * FROM bar; +----+-------------------+ | ID | INHALT | +----+-------------------+ | 1 | erster Datensatz | | 2 | zweiter Datensatz | +----+-------------------+ 2 rows in set (0.00 sec) _________________________________________________________________ Wie man sehen kann, wird das Feld "ID" automatisch erhöht. Logischerweise darf nur ein Feld mit dem auto_increment Attribut versehen werden. Zusätzlich muß dieses Feld als Index definiert werden - z.B. als Primär-Schlüssel (PRIMARY KEY). Um den Wert des letzten Inkrements erfahren, stellt PHP die Funktion mysql_insert_id() zur Verfügung: _________________________________________________________________
MySQL sagt: ".mysql_error()); // INSERT ausführen @mysql_query("INSERT bar SET INHALT='dritter Datensatz'"); // In unserem Beispiel ergibt das beim erstmaligen Aufruf "3" // dann "4", dann "5" etc. echo mysql_insert_id($linkID); ?> _________________________________________________________________ Die Funktion mysql_insert_id() liefert nichts zurück, wenn vorher keine INSERT-Query ausgeführt wurde; sie liefert einen falschen Wert, wenn der Typ des auto-increment -Feldes als BIGINT definiert wird, für die meisten Anwendungen sollte aber der Typ INT UNSIGNED mehr als ausreichend sein. 14.13. Wie lege ich den Initialwert des auto_increment fest? Läuft dieser Wert über? | [1763]ID | [1764]auto_increment | [1765]Nummer | [1766]Datentyp | [1767]Startwert | [1768]Ueberlauf | Antwort von [1769]Daniel T. Gorski Üblicherweise benutzt man Werte ohne Vorzeichen ( UNSIGNED ) für den Typ des auto_increment -Feldes. Ab der MySQL Server-Version 3.23 ist es auch nicht mehr möglich, in diesen Feldern negative Zahlen zu führen. Geeignete Datentypen für auto_increment wären: * TINYINT UNSIGNED - Wertebereich 0 bis 255. * SMALLINT UNSIGNED - Wertebereich 0 bis 65535. * MEDIUMINT UNSIGNED - Wertebereich 0 bis 16777215. * INT UNSIGNED - Wertebereich 0 bis 4294967295. Für die meisten Anwendungen ist der Datentyp INT UNSIGNED mehr als ausreichend - immerhin ermöglicht dieser eine Adressierung von über vier Milliarden Datensätzen! Der Initialwert des auto_increment -Feldes ist 1. Um diesen Wert zu ändern, muß man ihn explizit setzen: _________________________________________________________________ mysql> DELETE FROM bar; mysql> INSERT bar SET INHALT='erster Datensatz'; mysql> SELECT * FROM bar; +----+------------------+ | ID | INHALT | +----+------------------+ | 1 | erster Datensatz | +----+------------------+ mysql> INSERT bar SET ID='1000', INHALT='zweiter Datensatz'; mysql> SELECT * FROM bar; +------+-------------------+ | ID | INHALT | +------+-------------------+ | 1 | erster Datensatz | | 1000 | zweiter Datensatz | +------+-------------------+ mysql> INSERT bar SET INHALT='dritter Datensatz'; mysql> SELECT * FROM bar; +------+-------------------+ | ID | INHALT | +------+-------------------+ | 1 | erster Datensatz | | 1000 | zweiter Datensatz | | 1001 | dritter Datensatz | +------+-------------------+ _________________________________________________________________ Der auto_increment -Wert läuft nicht über - d.h. er wird nicht wieder negativ (bzw. Null bei UNSIGNED ), wenn er über seinen Wertebereich hinaus adressiert wird. Stattdessen wird MySQL einen Fehler melden: _________________________________________________________________ mysql> INSERT bar SET ID='4294967295', INHALT='letzter Datensatz'; mysql> SELECT * FROM bar; +------------+-------------------+ | ID | INHALT | +------------+-------------------+ | 1 | erster Datensatz | | 1000 | zweiter Datensatz | | 1001 | dritter Datensatz | | 4294967295 | letzter Datensatz | +------------+-------------------+ mysql> INSERT bar SET INHALT='geht noch einer rein?'; ERROR 1062: Duplicate entry '4294967295' for key 1 _________________________________________________________________ 14.14. Wie realisiere ich eine Volltextsuche mit MySQL? | [1770]SQL | [1771]Datenbank | [1772]MySQL | [1773]Suche | [1774]Volltextsuche | [1775]LIKE | [1776]Index | [1777]FULLTEXT | Antwort von [1778]Matthias P. Wuerfl Um eine Volltextsuche für eine Website zu realisieren, eignen sich speziell dafür erstellte Tools besser. Siehe hierzu auch [1779]Wie kann ich eine Volltextsuche realisieren? . Liegen die Inhalte der Website in einer MySQL-Tabelle, so kann man jedoch auch MySQL zur Suche verwenden. Für den "Hausgebrauch" sollte das auf wenig belasteten Servern oft reichen. MySQL bietet hierzu ab der Version 3.23.23 die Möglichkeit, einen Volltextindex anzulegen. Um die Spalte einer Tabelle mit einem solchen Index zu belegen muß das SQL-Statement ALTER TABLE tabellenname ADD FULLTEXT (textpalte) ausgeführt werden, welches einen entsprechenden Wortindex anlegt. Anschliessend kann mit einer Query wie SELECT * FROM tabellenname WHERE MATCH textspalte1 AGAINST ('suchtext') der Index durchsucht werden. Dieser Wortindex reagiert nur auf ganze Worte, es kann also nicht nach Teilworten oder Wortkombinationen gesucht werden. Die Suche nach "Bauer" findet also nicht "Bauernhof". Der Ausdruck MATCH a AGAINST b gibt einen Zahlenwert zurück, der die Relevanz des gefundenen Datensatzes wiedergibt, er kann also auch im SELECT -Teil eines SQL-Statements sinnvoll eingesetzt werden. Im ORDER BY -Teil des Statements braucht er nicht vorzukommen, denn MySQL sortiert automatisch nach Relevanz, wenn im WHERE -Teil der Volltextindex abgefragt wird. _________________________________________________________________ SELECT * FROM tabellenname WHERE MATCH textspalte AGAINST ('wort1 wort2') _________________________________________________________________ ...gibt alle Datensätze aus, in denen eines der Suchworte in der textspalte vorkommt - nach Relevanz absteigend sortiert. Hat man eine MySQL-Version älter als 3.23.23, dann kann man auch eine Volltextsuche realisieren, jedoch geht diese dann wesentlich langsamer vonstatten und belastet den Datenbankserver unverhältnismässig stark, da MySQL hier nicht den Index benutzen kann. _________________________________________________________________ SELECT * FROM tabellenname WHERE textspalte LIKE '%wort1%' OR textspalte LIKE '%wort2%' _________________________________________________________________ Das Prozentzeichen hat im LIKE-Statement von SQL die Funktion, die man in anderen Situationen auch vom Sternchen (*) her kennt. Diese Query findet auch Teilwörter. Die Suche nach "Bauer" findet also auch "Bauernhof". 14.15. Meine Datenbankabfrage/Mein SQL-Statement funktioniert nicht | [1780]SQL | [1781]Datenbank | [1782]MySQL | [1783]Fehler | [1784]Error | Antwort von Guido Haeger Häufig steht man vor dem Problem, dass ein SQL-Statement fehlerhaft ist und somit nicht zum gewünschten Ergebnis führt. Mit den in der Regel sehr aussagekräftigen Fehlermeldungen des MySQL-Servers ist es meist jedoch recht einfach, die Fehlerursache zu finden und zu beseitigen. Auf diese Fehlermeldungen kann man gezielt mit der Funktion [1785]mysql_error() zugreifen. _________________________________________________________________ function mysql_errorhandler($problem, $query = "") { echo "Datenbankfehler:
\n"; echo "Problem: $problem
\n"; if($query != "") { echo "Query: $query
\n"; } echo "MySQL: ".mysql_errno()." - ".mysql_error()."

\n"; } // Verbindung zum Datenbankserver herstellen if(!$db = @mysql_connect("host", "user", "password")) { mysql_errorhandler("Verbindungsaufbau gescheitert."); } // Datenbank auswählen if(!@mysql_select_db("database")) { mysql_errorhandler("Auswahl der Datenbank gescheitert."); } // Beispiel für ein SQL-Statement $query = "SELECT * FROM table WHERE x = '$x'"; $result = @mysql_query($query); if(!$result) { mysql_errorhandler("Datenbankabfrage gescheitert", $query); } _________________________________________________________________ Mit den Funktionen [1786]mysql_num_rows() bzw. [1787]mysql_affected_rows() kann man zusätzlich die Anzahl der gefundenen Datensätze bei einem SELECT-Statement bzw. die Anzahl der betroffenen Datensätze bei einem UPDATE-/INSERT-Statement überprüfen. _________________________________________________________________ // Beispiel für ein Select-Statement $query = "SELECT * FROM table WHERE x = '$x'"; $result = @mysql_query($query); if(!$result) { mysql_errorhandler("Datenbankabfrage gescheitert", $query); } else { echo mysql_num_rows()." Datensätze gefunden.
\n"; } // Beispiel für ein UPDATE-Statement $query = "UPDATE table SET a = '$a' WHERE x = '$x'"; $result = @mysql_query($query); if(!$result) { mysql_errorhandler("Datenbankabfrage gescheitert", $query); } else { echo mysql_affected_rows()." Datensätze geändert.
\n"; } _________________________________________________________________ Man kann sich das Fehler-Handling wesentlich vereinfachen, indem man z.B. die MySQL-Klasse der [1788]PHPLIB verwendet. Siehe auch: " [1789]Supplied argument is not a valid MySQL result... ". 14.16. Wie kann ich Umlaute richtig sortieren? | [1790]SQL | [1791]Datenbank | [1792]MySQL | [1793]Sortieren | [1794]Umlaute | Antwort von [1795]Sven Eichler Gelegentlich steht man vor dem Problem, Tabelleinhalt mit Text richtig sortiert auslesen zu müssen, welcher auch Umlaute enthält. Das ist aber bei MySQL nicht so ohne weiteres möglich, da MySQL standartmäßig einen Zeichensatz unterstützt, der eine Sortierung mit Umlauten nicht nach unserem natürlichen Verständnis unterstützt. Eine bessere Beschreibung dieses Problems findet sich in der [1796]MySQL-FAQ der Newsgroup de.comp.datenbanken.mysql. Anhand einer Tabelle mit Namen sei eine mögliche Lösung hier kurz erläutert. Follgende Tabelle ist gegeben: _________________________________________________________________ +----+------------+ | ID | Name | +----+------------+ | 1 | Ueberschuß | | 2 | Östreich | | 3 | Öhlers | +----+------------+ _________________________________________________________________ Diese Tabelle wird nun um eine Spalte erweitert, in der später die Namen stehen, nach denen sortiert werden kann. Ich nenne die Spalte einfach sortiert . Diese Spalte sollte die gleichen Eigenschaften haben, wie die Spalte Name . _________________________________________________________________ ALTER TABLE tabelle ADD sortiert VARCHAR(255) NOT NULL _________________________________________________________________ Nun sieht unsere Tabelle so aus: _________________________________________________________________ +----+------------+----------+ | ID | Name | sortiert | +----+------------+----------+ | 1 | Ueberschuß | | | 2 | Östreich | | | 3 | Öhlers | | +----+------------+----------+ _________________________________________________________________ Die Umwandlung der Umlaute kann nun mit einer einzigen MySQL-Anweisung erfolgen: _________________________________________________________________ mysql_query("UPDATE tabelle SET sortiert = REPLACE( REPLACE( REPLACE( REPLACE( REPLACE( REPLA CE( REPLACE(Name, 'Ä', 'A'), 'Ö', 'O'), 'Ü', 'U'), 'ä' , 'a'), 'ö', 'o'), 'ü','u'), 'ß', 's')"); _________________________________________________________________ ... hierbei ist Name die entsprechende Tabellenspalte, aus der die umzuwandelnden Namen genommen werden. Es dürfen für den Spaltennamen keine Hochkommas verwendet werden! Die fertige Tabelle sollte jetzt so aussehen: _________________________________________________________________ +----+------------+------------+ | ID | Name | sortiert | +----+------------+------------+ | 1 | Ueberschuß | Ueberschus | | 2 | Östreich | Ostreich | | 3 | Öhlers | Ohlers | +----+------------+------------+ _________________________________________________________________ Richtig sortiert lässt sich das ganze nun so auslesen: _________________________________________________________________ $result = mysql_query("SELECT * FROM tabelle ORDER BY sortiert"); _________________________________________________________________ Anwendung in laufenden Scripten mit MySQL: _________________________________________________________________ $name = "irgend ein Name"; mysql_query("INSERT INTO tabelle (Name, sortiert) VALUES('$name', REPLACE( REPLACE( REPLACE( REPLACE( REPLACE( REPLA CE( REPLACE(Name, 'Ä', 'A'), 'Ö', 'O'), 'Ü', 'U'), 'ä' , 'a'), 'ö', 'o'), 'ü','u'), 'ß', 's'))"); _________________________________________________________________ oder mit PHP: _________________________________________________________________ $name = "irgend ein Name"; $sortiert = strtr($name, "ÄÖÜäöüß", "AOUaous"); mysql_query("INSERT INTO tabelle (Name, sortiert) VALUES('$name','$sortiert')") ; _________________________________________________________________ 15. Datenbanken: Oracle 15.1. [1797]Ora oder OCI? 15.2. [1798]Ich habe Oracle-Support mit --with-oci8 in PHP eincompiliert, nun startet der Apache nicht mehr. 15.3. [1799]Der Webserver verbraucht jetzt viel mehr Speicher als ohne Oracle, mache ich was falsch? 15.4. [1800]Umlaute, die in die Datenbank eingetragen wurden, werden nicht korrekt dargestellt. 15.5. [1801]Gibt es auto_increment unter Oracle? 15.6. [1802]Ich verwende das obige Beispiel. Wie kann ich nun mysql_insert_id() emulieren? 15.7. [1803]Wie selektiere ich nur bestimmte Zeilen (LIMIT unter MySQL)? 15.8. [1804]Wie speichere ich Datensätze mit mehr als 2000 Zeichen ab? 15.9. [1805]Wie bearbeite ich LOBs mit PHP? 15.10. [1806]Wie nenne ich Spalten um? 15.11. [1807]Wie kann ich SQL Skriptdateien in Oracle ausführen? 15.12. [1808]Welche freien Tools gibts für Oracle? 15.13. [1809]Ich bekomme ein Oracle Fehlernummer ORA-XXXXX, wo stehen die Fehlercodes? 15.14. [1810]Welche Bücher zu Oracle sind empfehlenswert? 15.1. Ora oder OCI? | [1811]Oracle | [1812]Datenbank | [1813]Ora | [1814]OCI | [1815]Schnittstelle | [1816]LOB | Antwort von [1817]Thomas Fromm Wenn eine Oracle Version ab 8.0.4 zur Verfügung steht sollte die OCI Schnittstelle verwendet werden. Diese unterstützt z.B. LOBs (Large Objects) und wird zudem noch weiterentwickelt. 15.2. Ich habe Oracle-Support mit --with-oci8 in PHP eincompiliert, nun startet der Apache nicht mehr. | [1818]Oracle | [1819]kompilieren | [1820]OCI | [1821]Webserver | [1822]Apache | [1823]Library | [1824]Unterstuetzung | [1825]Fehler | Antwort von Anton Bangratz Die häufigste Ursache ist: Die shared libraries für den Oracle-Support werden nicht gefunden. Lösung: Das Verzeichnis $ORACLE_HOME/lib in /etc/ld.so.conf eintragen und ldconfig aufrufen. Eine weitere Möglichkeit wäre ein Fehler in der glibc-2.1. Behoben kann dies werden, indem den Apache neu gelinkt wird und dabei die Option -lpthread zu den LDFLAGS hinzugefügt wird. Dieser Fehler wurde in der glibc-2.2 behoben. linux:/usr/src/apache_1_3_14/ # CFLAGS='-lpthread' ./configure ... 15.3. Der Webserver verbraucht jetzt viel mehr Speicher als ohne Oracle, mache ich was falsch? | [1826]Oracle | [1827]Server | [1828]Speicher | [1829]Library | Antwort von [1830]Thomas Fromm Nein. Durch das Einbinden der Oracle Libs verbraucht der Webserver in der Regel erheblich mehr Speicher. Beim Apachen kann das schonmal auf 20 MB je Child anwachsen im Betrieb. 15.4. Umlaute, die in die Datenbank eingetragen wurden, werden nicht korrekt dargestellt. | [1831]Oracle | [1832]Datenbank | [1833]Umlaut | [1834]Zeichen | [1835]Sonderzeichen | [1836]Darstellung | [1837]Text | [1838]Zeichensatz | [1839]Sprache | [1840]Client | Antwort von Anton Bangratz Sowohl beim Eintragen als auch beim Auslesen der Daten in Textfeldern ist darauf zu achten, daß der verwendete Client die richtigen NLS/-Parameter verwendet. Die Clients bekommen diese Information über die Umgebungsvariablen NLS_LANG und ORA_NLS33 . Der richtige Wert für NLS_LANG ist der Datenbank selber zu entnehmen. Hat die Datenbank zum Beispiel als Character Set die Einstellung WE8ISO8859P9 , so sollte die Variable {SPRACHE}_{LAND}.WE8ISO8859P9 lauten, wobei {SPRACHE} und {LAND} nur für die Steuerung der Meldungen, die der Client zurückgibt, zuständig sind, und vom mit dem ORACLE-Client installierten Sprachpaket abhängig sind. Die Einstellung von ORA_NLS33 dient dazu, dem Client mitzuteilen, wo sich die Dateien befinden, die die Prompts in verschiedenen Sprachen beinhalten. Ein Beispiel für korrekte Einstellung: _________________________________________________________________ export ORACLE_HOME=/opt/oracle/OraHome1 export ORA_NLS33=$ORACLE_HOME/ocommon/nls/admin/data export NLS_LANG GERMAN_GERMANY.WE8ISO8859P9 _________________________________________________________________ Nun ist es noch wichtig, dass die Variablen von PHP korrekt initialisiert werden. Dabei ist darauf zu achten, dass man beim Modul-PHP diese Variablen vor dem Start des Apache setzt - im Script apachectl ist beispielsweise ein guter Platz dafür. 15.5. Gibt es auto_increment unter Oracle? | [1841]Oracle | [1842]Datenbank | [1843]Inkrement | [1844]automatisch | [1845]Zaehler | [1846]Trigger | [1847]ID | [1848]MySQL | [1849]Ersatz | Antwort von [1850]Thomas Fromm Nein. Jedoch kann auto_increment via Trigger emuliert werden. Beispiel: Wer von MySQL nach Oracle portiert vermisst wohl als erstes das auto_increment Feature von MySQL Spalten. Dieses läßt sich jedoch unter der Nutzung von Sequences und Triggern bei Oracle erstellen: _________________________________________________________________ rem Wir brauchen zum einen einen Zähler, rem der hochzählt (dazu die Sequence) create sequence zaehler_der_tabelle_xy increment by 1 start with 1 cache 2; rem Jetzt die eigentliche Tabelle CREATE TABLE xy ( id_xy NUMBER(20,0) PRIMARY KEY, bla_xy VARCHAR2(4000) ); rem Nun ist noch ein Trigger vonnöten, der rem die neue Id von der Sequence übergeben rem bekommt und vor dem insert diesen Wert rem auf die entsprechende Spalte überträgt: CREATE TRIGGER trigger_primary_key BEFORE INSERT ON xy REFERENCING NEW AS NEW OLD AS OLD FOR EACH ROW Begin select zaehler_der_tabelle_xy.nextval into :NEW.id_xy from DUAL; End; / _________________________________________________________________ Bei jedem insert wird nun der Wert von id_xy automatisch hochgesetzt. Zu beachten ist, daß es besser ist, für jede Tabelle eine eigene Sequence zu erstellen, denn sonst müssen sich alle Tabellen die fortlaufenden Zahlen teilen. Alternativ kann auch ohne Trigger gearbeitet werden und die nächste Zahl per Hand selektiert werden: _________________________________________________________________ $stmt=OCIParse($conn, "SELECT zaehler_der_tabelle_xy.nextval FROM DUAL"); OCIExecute($stmt); OCIFetch($stmt); $nextid=OCIResult($stmt, "NEXTVAL"); _________________________________________________________________ 15.6. Ich verwende das obige Beispiel. Wie kann ich nun mysql_insert_id() emulieren? | [1851]Oracle | [1852]Datenbank | [1853]MySQL | [1854]Ersatz | [1855]Zaehler | [1856]ID | [1857]letzter | [1858]Datensatz | [1859]mysql_insert_id | Antwort von [1860]Thomas Fromm Der aktuellen Wert der Sequenz zaehler_der_tabelle_xy kann durch Verwendung von currval (abgeleitet von Current Value) ermittelt werden. Mit dem folgenden Codestück ist es möglich, den zuletzt eingefügten Wert abzufragen: _________________________________________________________________ // INSERT in die Tabelle $stmt = OCIParse($conn, "INSERT INTO xy (BLA_XY) VALUES 'BLA'"); OCIExecute($stmt, OCI_DEFAULT); //Abfrage der Sequence $stmt = OCIParse($conn,"SELECT zaehler_der_tabelle_xy.currval AS CV FROM DUAL"); OCIExecute($stmt, OCI_DEFAULT); OCIFetch($stmt); $last_id=OCIResult($stmt, "CV"); OCICommit($conn); _________________________________________________________________ Achtung: Das selektieren des currval funktioniert nur innerhalb derselben Transaktion (daher auch beim OCI_DEFAULT). Will man den aktuellen höchsten ID-Wert ermitteln, ist es besser, unter Verwendung der SQL-Funktion MAX() den höchsten Wert direkt aus der Tabelle abzufragen. 15.7. Wie selektiere ich nur bestimmte Zeilen (LIMIT unter MySQL)? | [1861]Oracle | [1862]Datenbank | [1863]Limit | [1864]MySQL | [1865]Ersatz | [1866]bestimmt | [1867]Anzahl | [1868]Zeilen | Antwort von [1869]Thomas Fromm Da Oracle über kein Limit verfügt gestaltet sich die Abfrage etwas komplizierter (das Beispiel funktioniert erst ab Version 8.1): _________________________________________________________________ SELECT * FROM (SELECT ROWNUM rownum2, inline_view1.* FROM (SELECT ROWNUM rownum1, ename, hiredate FROM emp ORDER BY hiredate ) inline_view1 -- zum Sortieren (ROWNUM hier noch ungeordnet) ) inline_view2 -- ROWNUM spiegelt jetzt die Sortierung wider WHERE rownum2 BETWEEN 5 AND 7 _________________________________________________________________ Dies ist nicht sonderlich schnell, weil die innere Abfrage alle Zeilen auswählt. Für Versionen ab 8.1.6 geht auch folgendes: _________________________________________________________________ SELECT * FROM (SELECT ROW_NUMBER() OVER(ORDER BY hiredate) rownum1, ename, hiredate FROM emp ) inline_view1 WHERE rownum1 BETWEEN 5 AND 7 / _________________________________________________________________ Will man lediglich n Zeilen ausgeben tuts auch dies: _________________________________________________________________ SELECT * FROM (SELECT ename, hiredate FROM emp ORDER BY hiredate) WHERE ROWNUM < 6 _________________________________________________________________ 15.8. Wie speichere ich Datensätze mit mehr als 2000 Zeichen ab? | [1870]Oracle | [1871]Datenbank | [1872]speichern | [1873]Limit | [1874]Groesse | [1875]Zeichen | [1876]Laenge | Antwort von [1877]Thomas Fromm Um diese Datenfelder abzuspeichern, muss zuerst Speicher angefordert werden, dies geschieht mit [1878]OCIBindByName() . _________________________________________________________________ $req="INSERT INTO wurstbrote (name) VALUES (:name)"; $stmt=OCIParse($req); // nun binde ich den Inhalt von $wurstbrotname // an den Oracle Platzhalter :name OCIBindByName($stmt,":name",$wurstbrotname,-1); OCIExecute($stmt); _________________________________________________________________ Trotz dieser Umständlichkeit gestaltet sich das Lesen/Schreiben von grösseren Datensätzen performanter als z.B. bei MySQL. 15.9. Wie bearbeite ich LOBs mit PHP? | [1879]Oracle | [1880]Datenbank | [1881]LOB | [1882]speichern | Antwort von [1883]Thomas Fromm Diese Codebeispiele beschreiben insert , update und select : _________________________________________________________________ // INSERT: $req = "INSERT INTO bdata (description, data) VALUES (:description, EMPTY_BLOB()) returning data into :data"; $stmt = OCIParse($conn, $req); $lob = OCINewDescriptor($conn, OCI_D_LOB); OCIBindByName($stmt, ":description", $description, -1); OCIBindByName($stmt, ":data", $lob, -1, OCI_B_BLOB); OCIExecute($stmt, OCI_DEFAULT); if($lob->save($bdata)) { OCICommit($conn); } else { echo "Problems: Couldn't upload Lob\n"; } OCIFreeDesc($lob); OCIFreeStatement($stmt); // Für die verwendeten Typen beim OCIBindByName auf jeden Fall mal // ins PHP Handbuch schauen. // UPDATE: // Das SELECT FOR UPDATE sperrt den Eintrag für // andere Schreibzugriffe // Diese Sperre wird erst beim nächsten Commit aufgehoben $req="SELECT data FROM bdata WHERE id='5' FOR UPDATE"; $stmt=OCIParse($req); OCIExecute($stmt, OCI_DEFAULT); $req="UPDATE bdata SET data=:data WHERE id='5'"; $stmt=OCIParse($req); OCIBindByName($stmt, ":data", $data, -1); OCIExecute($stmt, OCI_DEFAULT); OCICommit($conn); // SELECT $req="SELECT data FROM bdata WHERE id='5'"; $stmt=OCIParse($req); OCIExecute($stmt); OCIFetch($stmt); $bdatalob=OCIResult($stmt, "DATA"); $bdata=$bdatalob->load(); _________________________________________________________________ 15.10. Wie nenne ich Spalten um? | [1884]Oracle | [1885]Datenbank | [1886]umbenennen | [1887]Spalte | [1888]Name | [1889]aendern | [1890]loeschen | Antwort von [1891]Thomas Fromm Oracle bietet dazu nicht direkt eine Möglichkeit. Sofern man DBA Rechte hat kann man dies über einen Midnighthack lösen: (Ist mit Vorsicht zu geniessen und am besten nicht zu benutzen :-) _________________________________________________________________ update SYS.COL$ col set col.NAME = 'neuer_name' where col.NAME = 'alter_name' and col.OBJ# in ( select ob.OBJ# from SYS.OBJ$ ob, SYS.USER$ us where ob.OWNER# = us.USER# and us.NAME = 'besitzername' and ob.NAME = 'alter_name' ); _________________________________________________________________ Besitzername ist der Name des Besitzers der Tabelle. Wichtig: Alle Namen müssen in Grossbuchstaben angegeben werden! 15.11. Wie kann ich SQL Skriptdateien in Oracle ausführen? | [1892]Oracle | [1893]Datenbank | [1894]SQL | [1895]Skript | Antwort von [1896]Thomas Fromm Einfach SQL*Plus starten und dann: _________________________________________________________________ SQL> @meinedatei.sql _________________________________________________________________ 15.12. Welche freien Tools gibts für Oracle? | [1897]Oracle | [1898]Datenbank | [1899]Tool | [1900]kostenlos | [1901]phpOracleAdmin | [1902]phpMyAdmin | [1903]Windows | [1904]Programm | Antwort von [1905]Thomas Fromm Zu empfehlen ist der Oracle Objectmanager von [1906]OraSoft . Seit kurzem gibt es auch ein ähnliches in PHP geschriebenes Tool [1907]phpOracleAdmin . Wer TOAD von Windows gewohnt ist, wird sich auf [1908]TOra freuen, dies ist eine freie, ja man könnte sagen, PL/SQL IDE. 15.13. Ich bekomme ein Oracle Fehlernummer ORA-XXXXX, wo stehen die Fehlercodes? | [1909]Oracle | [1910]Datenbank | [1911]Fehler | [1912]Code | [1913]Nummer | [1914]Beschreibung | [1915]deutsch | Antwort von [1916]Thomas Fromm Oracle Fehlermeldungen bestehen aus einem Fehlerbereich (ORA, OCI ...) und einer 5-stelligen Fehlernummer. Die komplette Fehlerbeschreibung bekommt man mit: _________________________________________________________________ linux:# / oerr ora _________________________________________________________________ Vorrausgesetzt die Pfade ins $ORACLE_HOME/bin sind gesetzt, erscheint der volle Fehlertext. Kommt allerdings eine Fehlermeldung der Art: _________________________________________________________________ Cannot find /u01/8.1.6/rdbms/mesg/orad.msg file. _________________________________________________________________ ist die Oracle Installation eine teilweise ans deutsche angepasste Version. (oraus.msg ist die original Datei und orad.msg ist eine deutsche Version) Da die orad.msg nciht in allen Fällen vorhanden ist, empfiehlt es sich einen symbolischen Link zu setzen. _________________________________________________________________ linux:# / cd /u01/8.1.6/rdbms/mesg/ linux:# / ln -s oraus.msg orad.msg _________________________________________________________________ Dannach sollte man zumindest die englischen Fehlertexte erhalten. 15.14. Welche Bücher zu Oracle sind empfehlenswert? | [1917]Oracle | [1918]Datenbank | [1919]Buch | [1920]lernen | Antwort von [1921]Thomas Fromm Für das nötige Basiswissen empfiehlt sich die " [1922]Oracle Referenz ", die auch Einsteigern u.a. den Zugang zu PL/SQL erleichtert. " [1923]Oracle8 für den DBA " trägt zum alltäglichen Umgang mit Oracle bei. Gerade im produktiven Einsatz, zeigt sich, das die administrative Seite der Datenbank nicht zu unterschätzen ist. Gerade bei Webapplikationen ist Performance und Reaktionszeit wichtig, ich empfehle hier " [1924]Oracle 8. Tuning. ". Während die Oracle Referenz ins Regal eines jeden Entwicklers gehört, der auf dieser Datenbank Applikationen entwickelt, bieten die beiden anderen Bücher eine Abrundung der Nachschlagewerke für den allgemeinen Umgang. Die dort aufgeführten Beispiele sind verständlich geschrieben und leicht nachzuvollziehen. (Ein bisschen gewöhnungsbedürftig ist allerdings bei Oracle Press Bücherübersetzungen die Indizierung...) Wenn man mehr auf Optimierung und Performance ausgerichtet ist, dem kann ich nur " [1925]Oracle PL/SQL Programmierung " ans Herz legen. Neben einer ausführlichen Behandlung von PL/SQL werden dort auch wichtige Packages wie z.B. DBMS_JOB, der Oracle interne Cronjobmechanismus erklärt. Zusätzlich gibt es auch ein Kapitel, welches die Einbindung von Externen Prozeduren (welche man in C oder Java Programmieren kann) erläutert anhand von Beispielen. 16. phpMyAdmin 16.1. [1926]Was ist phpMyAdmin? 16.2. [1927]Ich bin kein MySQL-Administrator. Wie kann ich phpMyAdmin nur für mich selbst installieren? 16.3. [1928]Ich bin MySQL-Administrator und möchte ein Exemplar phpAdmin für alle meine User installieren. 16.4. [1929]Wieso kann ich den Inhalt meiner Tabelle nicht editieren? 16.5. [1930]Wieso werden TIMESTAMP-Felder nicht auf die aktuelle Zeit gesetzt, wenn ich eine neue Zeile einfüge? 16.6. [1931]Wieso kann ich in phpMyAdmin mehrere durch Semikolon getrennte SQL-Statements ausführen, nicht aber mit normalen PHP-Funktionen? 16.1. Was ist phpMyAdmin? | [1932]MySQL | [1933]phpMyAdmin | [1934]Verwaltung | [1935]Oberflaeche | Antwort von [1936]Tobias Ratschiller phpMyAdmin ist eine in PHP geschriebene Verwaltungsoberfläche für MySQL. Weitere Informationen dazu finden Sie auf [1937]der Homepage . 16.2. Ich bin kein MySQL-Administrator. Wie kann ich phpMyAdmin nur für mich selbst installieren? | [1938]MySQL | [1939]phpMyAdmin | [1940]Administrator | [1941]Benutzer | [1942]installieren | Antwort von [1943]Tobias Ratschiller Holen Sie sich die Distribution (TarGz or Zip) von der [1944]phpMyAdmin-Homepage . Bitte folgen Sie dann den Anweisungen in der Datei INSTALL; für's erste Ausprobieren genügt es, in die Datei config.inc.php3 Ihren MySQL-Benutzernamen und -Passwort einzutragen. 16.3. Ich bin MySQL-Administrator und möchte ein Exemplar phpAdmin für alle meine User installieren. | [1945]MySQL | [1946]phpMyAdmin | [1947]Administrator | [1948]Benutzer | [1949]installieren | Antwort von [1950]Tobias Ratschiller Seit phpMyAdmin 2.0.3 ist es möglich, eine zentrale Kopie von phpMyAdmin zu installieren, in die sich die einzelnen Benutzer mit Benutzername und Passwort einloggen. phpMyAdmin benutzt dafür das Rechte-System von MySQL. Benutzer müssen daher korrekt in das Rechte-System eingetragen sein: Für jeden Benutzer, der auf phpMyAdmin zugreifen können soll, muss ein Eintrag in die mysql.user und mysql.db -Tabelle gemacht werden. Um dem Benutzer foo Zugriff auf die Datenbank foo_db zu geben, würden Sie folgende SQL-Statements benutzen: _________________________________________________________________ INSERT INTO user (Host, User, Password, Select_priv, Insert_priv, Update_priv, Delete_priv, Create_priv, Drop_priv, Reload_priv, Shutdown_priv, Process_priv, File_priv, Grant_priv, References_priv, Index_priv, Alter_priv) VALUES ('localhost', 'foo', PASSWORD('bar'), 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N', 'N') INSERT INTO db (Host, Db, User, Select_priv, Insert_priv, Update_priv, Delete_priv, Create_priv, Drop_priv, Grant_priv, References_priv, Index_priv, Alter_priv) VALUES ('localhost', 'foo_db', 'foo', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', '', '', '', '') _________________________________________________________________ Bitte beachten Sie, daß Sie nach dieser Änderung ein FLUSH PRIVILEGES -Statement ausführen müssen, damit sie wirksam wird. Seit phpMyAdmin 2.0.6 werden auch Wildcards im Rechte-System unterstützt; damit können Sie dem Benutzer foo beispielsweise Zugriff auf alle Datenbanken geben, deren Name mit foo_ beginnt. Weitere Informationen zum Setup eines solchen Benutzers finden Sie im MySQL-Handbuch. 16.4. Wieso kann ich den Inhalt meiner Tabelle nicht editieren? | [1951]MySQL | [1952]phpMyAdmin | [1953]bearbeiten | [1954]Tabelle | [1955]Inhalt | [1956]primary key | Antwort von [1957]Tobias Ratschiller Bis phpMyAdmin 2.0.6 können Sie den Inhalt einer Tabelle nur dann ändern, wenn ein Primärschlüssel in der Tabelle gesetzt ist. Ab 2.0.6 können Sie den Inhalt in allen Fällen editieren. Falls kein Primärschlüssel existiert, wird allerdings der Inhalt aller Zeilen mit zu der aktuellen Zeile äquivalenten Inhalten geändert, da es im relationalen Datenbankmodell unmöglich ist, diese Zeilen voneinander zu unterscheiden. Es ist gutes Datenbankdesign, wenn man für eine jede Tabelle eine Spalte (atomarer Primärschlüssel) oder eine Kombination von Spalten (zusammengesetzter Primärschlüssel) als Primärschlüssel deklariert, sodaß keine zwei Zeilen existieren können, die in den als Primärschlüssel deklarierten Spalten dieselben Werte haben können. Alle Zeilen werden durch ihre Primärschlüsselwerte überhaupt erst unterscheidbar. 16.5. Wieso werden TIMESTAMP-Felder nicht auf die aktuelle Zeit gesetzt, wenn ich eine neue Zeile einfüge? | [1958]MySQL | [1959]phpMyAdmin | [1960]timestamp | [1961]NULL | [1962]leer | Antwort von [1963]Tobias Ratschiller phpMyAdmin trägt einen leeren String ( '' ) ein, wenn Sie keinen Wert angeben. MySQL konvertiert dies zu 0000-00-00 00:00:00 . Um den Default-Wert eines Feldes einzutragen (im Fallse von TIMESTAMP die aktuelle Zeit) geben Sie im Eintragsformular "null" (ohne Anführungszeichen) als Wert ein. 16.6. Wieso kann ich in phpMyAdmin mehrere durch Semikolon getrennte SQL-Statements ausführen, nicht aber mit normalen PHP-Funktionen? | [1964]MySQL | [1965]phpMyAdmin | [1966]SQL | [1967]Semikolon | Antwort von [1968]Tobias Ratschiller SQL selbst definiert nur einzelne Anweisungen, keine Anweisungsfolgen. phpMyAdmin trennt die Zeichenkette auf und generiert dann automatisch mehrere einzelne Anfragen. PHP selbst macht das nicht, also müssen Sie selbst die Anweisungen nacheinander einzeln senden. 17. Grafikfunktionen 17.1. [1969]Wie kann ich Thumbnails von einer Webseite erzeugen lassen? 17.2. [1970]Wie kann ich mit PHP Diagramme erstellen? 17.1. Wie kann ich Thumbnails von einer Webseite erzeugen lassen? | [1971]Grafik | [1972]Thumbnail | [1973]Webseite | [1974]URL | [1975]Vorschau | Antwort von [1976]Kristian Köhntopp KDE bietet das Kommandozeilenprogramm kwebdesktop an, das vom KDE HTML Widget Gebrauch macht. Dieses Programm lädt eine Webseite und wandelt sie in ein PNG-Bild um. Es wird folgendermaßen aufgerufen: kwebdesktop x y datei url x ist die Breite des Bildes in Pixeln, y die Höhe des Bildes in Pixeln, datei der Name der zu erzeugenden PNG-Datei und url die URL der zu ladenden Seite. In Suse Linux ist kwebdesktop Bestandteil des Paketes kdebase, getestet wurde Version 2.2.1. 17.2. Wie kann ich mit PHP Diagramme erstellen? | [1977]Graphen | [1978]Diagramm | [1979]Balken | [1980]Torten | [1981]Statistik | [1982]Grafik | Antwort von [1983]Johannes Frömter Es gibt mehrere fertige Scripte, die die Erzeugung von Diagrammen übernehmen. Eine Auflistung findet sich hier: [1984]Welche Lösungen gibt es, um Diagramme zu erstellen? 18. PDF-Dateien 18.1. [1985]Was ist PDF? 18.2. [1986]Kann ich PDF-Dateien mit PHP erstellen? 18.3. [1987]Kann ich die PDF-Bibliotheken kommerziell nutzen? 18.4. [1988]Gibt es einen anderen Weg, um PDF-Dateien zu erzeugen? 18.5. [1989]Wo finde ich Details zum PDF-Format? 18.6. [1990]Kann ich die PDF-Referenz auch als Buch bekommen? 18.7. [1991]Ist die Erstellung von PDF-Dateien auf Basis der Datei-Spezifikationen kostenlos? 18.8. [1992]Welche Maßeinheit wird im PDF-Format verwendet? 18.9. [1993]Kann ich für Höhe und Breite unterschiedliche Maßstäbe verwenden? 18.10. [1994]Kann ich bestehende PDF-Dateien als Template für dynamische Dokumente verwenden? 18.11. [1995]Kann ich meine PDF-Dateien irgendwie schützen? 18.12. [1996]Gibt es eine Rechteverwaltung für PDF-Dateien? 18.13. [1997]Wie erzeuge ich geschütze PDF-Dateien? 18.14. [1998]Kann ich sicher feststellen, ob meine Besucher PDF-Dateien lesen können? 18.15. [1999]Welchen MIME-Type muß ich für PDF-Dateien verwenden? 18.16. [2000]Warum meldet mein Browser 'Call to undefined function ...' wenn ich die PDF-Funktionen von PHP verwende? 18.17. [2001]Wie sehe ich, ob meine PHP-Installation die PDF-Bibliotheken benutzen kann? 18.18. [2002]Wie mache ich die PDF-Bibliotheken für meine Installation verfügbar? 18.1. Was ist PDF? | [2003]PDF | Antwort von Kai Schröder PDF ist das Portable Document Format. Es wurde von der Firma Adobe entwickelt und hat sich als weit verbreitetes Format für den elektronischen Austausch von Dokumenten etabliert. Adobe PDF ermöglicht den Austausch von Dokumenten im originalen Layout unabhängig von der zur Darstellung genutzen Plattform. Zur Anzeige benötigt man den kostenlosen [2004]Adobe Acrobat Reader . 18.2. Kann ich PDF-Dateien mit PHP erstellen? | [2005]PDF | Antwort von Kai Schröder Ja. Es gibt für die Erstellung von PDF-Dateien mit PHP zwei Bibliotheken: die [2006]PDFlib von Thomas Merz und FastIO's [2007]ClibPDF . 18.3. Kann ich die PDF-Bibliotheken kommerziell nutzen? | [2008]PDF | [2009]kommerziell | [2010]Lizenz | Antwort von Kai Schröder Ja, du kannst. Dafür brauchst du aber eine spezielle Lizenz für die von dir verwendete Bibliothek. Details für die kommerzielle Nutzung von PDFlib findest du unter [2011]http://www.pdflib.com/pdflib/business.html . Den Lizenzvertrag zur Nutzung von ClibPDF findest du unter [2012]http://www.fastio.com/licensePlain.html . 18.4. Gibt es einen anderen Weg, um PDF-Dateien zu erzeugen? | [2013]PDF | Antwort von Kai Schröder Ja. PDF-Dateien sind Text-Dateien, die bestimmte Steuersequenzen enthalten. Diese Steuersequenzen sind durch Adobe veröffentlicht worden und können wie normaler Text in eine Datei geschrieben werden. 18.5. Wo finde ich Details zum PDF-Format? | [2014]PDF | [2015]Handbuch | Antwort von Kai Schröder Adobe veröffentlicht die Details zum PDF-Format im Rahmen des Adobe Solutions Network (ASN). Die Dateiformat- Spezifikationen zu PDF findest du unter [2016]http://partners.adobe.com/asn/developer/acrosdk/docs.html#filefm tspec s . 18.6. Kann ich die PDF-Referenz auch als Buch bekommen? | [2017]PDF | [2018]Handbuch | Antwort von Kai Schröder Ja. In Zusammenarbeit mit Adobe sind im Addison-Wesley-Verlag bisher folgende beiden Bücher erschienen: [2019]PDF Reference, Second Edition, Version 1.3 und [2020]PDF Reference, Third Edition, Version 1.4 . 18.7. Ist die Erstellung von PDF-Dateien auf Basis der Datei-Spezifikationen kostenlos? | [2021]PDF | [2022]Patent | [2023]Lizenz | [2024]Kompression | [2025]LZW | Antwort von Kai Schröder Jein. Im PDF-Format ist die Verwendung der LZW-Kompression möglich. Diese ist durch das US-Patent No. 4,558,302 geschützt, welches der Firma Unisys Corporation gehört. Wenn du also LZW in deinen PDF-Files benutzen willst, so mußt du dir dafür die Lizenz von Unisys holen. Weitere Informationen dazu gibt es bei Welch Licensing Department, Law Department, M/S C2SW1, Unisys Corporation, Blue Bell, Pennsylvania, 19424. 18.8. Welche Maßeinheit wird im PDF-Format verwendet? | [2026]PDF | [2027]Einheit | Antwort von Kai Schröder Laut PHP-Manual sind alle Längen- und Koordinatenangaben in Postscript-Punkten gemessen. Für gewöhnlich entsprechen 72 PostScript-Punkte 1 Inch (72 dpi, 1 Inch = 2,54cm), was jedoch von der Auflösung des Ausgabegeräts abhängt (Drucker verwenden meist 300 oder 600dpi). Die Spezifikation von Adobe überläßt die Wahl der Maßeinteilung dem Entwickler. Eine PDF-Seite ist immer im Format DIN A4, also 279 Millimeter hoch und 210 Millimeter breit. Beim Anlegen einer neuen PDF-Seite muß eine Höhe und eine Breite in Punkten angegeben werden. Diese Angaben bilden dann den Maßstab für das Koordinatensystem. Dieser berechnet sich wie folgt: Punktanzahl durch Kantenlänge (in Millimetern). Definiert man also die Breite der Seite mit 2100 Punkten, so ergibt sich ein Maßstab von 10 Punkten pro Millimeter (2100 Punkte geteilt durch 210 mm). 18.9. Kann ich für Höhe und Breite unterschiedliche Maßstäbe verwenden? | [2028]PDF | [2029]Einheit | Antwort von Kai Schröder Ja. Dies ist aber nicht zu empfehlen, da für die verzerrungsfreie Darstellung von Quadraten und Kreisen die unterschiedlichen Maßstäbe erst auf eine gemeinsame Basis gebracht werden müssen. 18.10. Kann ich bestehende PDF-Dateien als Template für dynamische Dokumente verwenden? | [2030]PDF | [2031]Vorlage | Antwort von Kai Schröder Jein, nicht in jedem Fall. Ob sich PDF-Dateien als Template eignen, hängt von verschiedenen Faktoren ab. Zum ersten kann man mittels Adobe Acrobat ein PDF optimieren (siehe Kapitel F der PDF-Referenz). Dabei wird das PDF in seiner Struktur verändert und zum Teil komprimiert. Zum anderen sind für PDF-Dateien einige Sicherheitseinstellungen möglich, die die nachträgliche Veränderung nicht erlauben. Ein nicht optimiertes und ungeschütztes PDF läßt sich aber als Template verwenden. 18.11. Kann ich meine PDF-Dateien irgendwie schützen? | [2032]PDF | [2033]Schutz | [2034]Verschlüsselung | Antwort von Kai Schröder Ja. Adobe hat die Verschlüsselung von PDF-Dokumenten mittels einer Kombination aus MD5-Hashes und RS4 vorgesehen. Näheres findet sich in der PDF-Referenz: 3.5 Encryption. 18.12. Gibt es eine Rechteverwaltung für PDF-Dateien? | [2035]PDF | [2036]Benutzerverwaltung | Antwort von Kai Schröder Ja, allerdings gibt es nur zwei verschiedene Rollen. Es wird zwischen dem Besitzer und dem Benutzer unterschieden. Beide Benutzer verwenden unterschiedliche Passwörter. Der Besitzer hat nach Eingabe seines Passwortes volle Zugriffsrechte (einschließlich aller Veränderungen), die Rechte des Benutzers regeln die Einstellungen der Sicherheitsoptionen. 18.13. Wie erzeuge ich geschütze PDF-Dateien? | [2037]PDF | [2038]Schutz | [2039]Verschlüsselung | Antwort von Kai Schröder Die Details zur Vergabe der Rechte und die Generierung der Passwörter findest du in der PDF-Referenz: 3.5.2 Standart Security Handler. 18.14. Kann ich sicher feststellen, ob meine Besucher PDF-Dateien lesen können? | [2040]PDF | [2041]Plugin | Antwort von Kai Schröder Nein. Du kannst zwar den Inhalt der Variable $HTTP_ACCEPT prüfen, diese enthält aber nicht unbedingt die richtigen Werte. Eine Angabe wie "*/*" (alle MIME-Types werden unterstützt) hilft dir nicht weiter. Du solltest deine User darauf hinweisen, das sie den Adobe Acrobat Reader brauchen und ihnen eine Downloadquelle anbieten. 18.15. Welchen MIME-Type muß ich für PDF-Dateien verwenden? | [2042]PDF | [2043]mime | [2044]header | [2045]Content-Type | Antwort von Kai Schröder Der korrekte MIME-Type für PDF-Dateien lautet "application/pdf". Du kannst ihn mittels header("Content-type: application/pdf") an den Browser senden. 18.16. Warum meldet mein Browser 'Call to undefined function ...' wenn ich die PDF-Funktionen von PHP verwende? | [2046]PDF | [2047]undefined | Antwort von Kai Schröder Du hast die PDF-Funktionen nicht mit installiert oder sie sind in der php.ini nicht aktiviert. 18.17. Wie sehe ich, ob meine PHP-Installation die PDF-Bibliotheken benutzen kann? | [2048]PDF | [2049]extension | Antwort von Kai Schröder Zur Laufzeit kannst du die Funktion [2050]extension_loaded() verwenden, um zu testen, ob die von dir benötigte Bibliothek zur Verfügung steht. Desweiteren kannst du Ausgabe von [2051]phpinfo() durchschauen. Näheres dazu findest du in der PHP-FAQ: [2052]Wie finde ich heraus, wie mein PHP-Interpreter konfiguriert ist? . 18.18. Wie mache ich die PDF-Bibliotheken für meine Installation verfügbar? | [2053]PDF | [2054]extension | [2055]installieren | Antwort von Kai Schröder Wenn du deine PHP-Installation selbst kompiliert hast, dann lies bitte unter [2056]Komplette Liste der Konfigurationsoptionen nach, welche Optionen du für configure verwenden mußt. Desweiteren gibt es in der PHP-FAQ ein ganzes Kapitel zum Thema: [2057]Installation und Inbetriebnahme . 19. PHPLIB 19.1. [2058]Was ist PHPLIB? 19.2. [2059]Wo kann ich PHPLIB bekommen? 19.3. [2060]Mein Provider hat PHPLIB nicht installiert. 19.4. [2061]Ich habe keinen Zugriff auf die php.ini. 19.5. [2062]"Oops, php3_SetCookie called after header has been sent!" 19.6. [2063]GET-Mode oder Cookie-Mode? Sind Cookies böse? 19.7. [2064]Was ist das Sevenval-Patent? 19.8. [2065]Warum verwendet PHPLIB nicht die IP-Nummer des Browsers als Schutz gegen eine Übernahme der Session? 19.9. [2066]Warum sind die Session-IDs von PHPLIB so lang? 19.10. [2067]Was schreibe ich denn nun in meine local.inc? 19.11. [2068]ERROR 1146: Table 'xyz.active_sessions' doesn't exist! 19.12. [2069]Wie kann ich mit PHPLIB und Frames arbeiten? 19.13. [2070]Internet Explorer: Meine Seiten werden nicht aktualisiert. 19.14. [2071]Wie kann ich Reloads durch den User erkennen und verhindern? 19.15. [2072]Wie kann ich meine Variablen initialisieren und registrieren? 19.16. [2073]Wie kann ich auto_init benutzen, um Session-Statistiken zu erfassen? 19.17. [2074]Wie kann ich eine Datei mit einem Paßwort schützen? 19.18. [2075]Wie kann ich mich gegen einen LDAP-Server authentisieren? 19.19. [2076]Wie kann ich Zugriffsrechte in PHPLIB definieren? 19.20. [2077]Wie kann ich einen Warenkorb realisieren? 19.21. [2078]Wie kann ich eine Menünavigation erzeugen? 19.22. [2079]Was sind Templates? Warum sind Templates nützlich? 19.23. [2080]Was ist default Authentication? Warum ist das nützlich? 19.1. Was ist PHPLIB? | [2081]PHPLIB | [2082]Was ist | [2083]Zweck | [2084]Session | [2085]Login | [2086]Datenbank | [2087]Formular | Antwort von [2088]Kristian Köhntopp [2089]PHPLIB ist eine Sammlung von Klassen, mit denen man Webanwendungen einfacher und sicherer schreiben kann. PHPLIB realisiert Sessions und Sessionvariablen, also Variablen, die ihren Wert am Ende einer Seite behalten und auf die nächste Seite mitgeschleppt werden. Dadurch braucht man solche Variablen nicht mehr in HIDDEN -Feldern von Formular zu Formular durchzuschleifen. Auf der Grundlage solcher Variablen implementiert PHPLIB dann eine Reihe von weiteren Funktionen, zum Beispiel eine bessere und flexiblere Benutzerauthentisierung, als dies mit dem Webserver oder blanken PHP möglich ist, und ein System zur Kontrolle von Zugriffsrechten auf Webseiten und deren Funktionen. Außerdem enthält PHPLIB eine Reihe von unabhängigen Klassen, die Bedienelemente für Webseiten und Hilfsfunktionalität zur Handhabung von Formularen bereitstellen. 19.2. Wo kann ich PHPLIB bekommen? | [2090]PHPLIB | [2091]Download | [2092]Homepage | [2093]Anleitung | Antwort von [2094]Kristian Köhntopp Die Website von PHPLIB ist [2095]http://phplib.sourceforge.net/ . Die Entwicklung von PHPLIB wurde durch [2096]NetUSE AG gefördert. Informationen über PHPLIB finden sich in englischer Sprache als Bestandteil des Downloads und auf der Website. Außerdem existiert noch eine deutsche Anleitung unter der URL [2097]http://www.koehntopp.de/kris/artikel/phplib-deutsch/ . Dieser Text bildet außerdem Kapitel 24 von [2098]PHP 4. Dynamische Webauftritte professionell realisieren . Er ist mit dem 3. Reprint der 1. Auflage dazugekommen. 19.3. Mein Provider hat PHPLIB nicht installiert. | [2099]PHPLIB | [2100]Provider | [2101]Installation | [2102]php.ini | [2103]Konfiguration | Antwort von [2104]Kristian Köhntopp PHPLIB ist eine Sammlung von PHP3-Scripten. Sofern man bei einem Provider PHP3 ausführen kann, kann man dort PHPLIB installieren und anwenden. Die Verwendung von PHPLIB ist einfacher, wenn der Provider den Zugriff auf die php3.ini gestattet oder das Setzen gewisser Konfigurationsvariablen auf andere Weise erlaubt. PHPLIB funktioniert besser, wenn [2105]track_vars eingeschaltet sind und [2106]include_path und [2107]auto_prepend_file korrekt gesetzt sind. 19.4. Ich habe keinen Zugriff auf die php.ini. | [2108]PHPLIB | [2109]php.ini | [2110]Provider | [2111]Installation | [2112]Konfiguration | Antwort von [2113]Kristian Köhntopp In der Anleitung zu PHPLIB ist die Rede davon, verschiedene Parameter in der php.ini anzupassen. Wenn man bei einem Webhoster untergebracht ist, der keinen Zugriff auf diese Parameter gestattet, hat man verschiedene Optionen, PHPLIB dennoch zu verwenden. * Man kann zu einem Webhoster gehen, der mehr Komfort bietet und PHP besser unterstützt. Dies ist die empfohlene Vorgehensweise. * PHPLIB benötigt Zugriff auf die track_vars , namentlich $HTTP_GET_VARS , $HTTP_POST_VARS und $HTTP_COOKIE_VARS . Dies ist nur der Fall, wenn [2114]phpinfo() die Variable [2115]track_vars als eingeschaltet ausweist. Um diese Einschränkung kann man herumarbeiten, wenn man sein Script ganz am Anfang mit der Anweisung beginnen läßt. Dabei ist zu beachten, daß dies ein Lexer-Token ist, das ganz genau so geschrieben werden muß und das, damit PHPLIB funktioniert, von keinen Leerzeichen gefolgt werden darf. Eine Seite muß also so beginnen: _____________________________________________________________ _____________________________________________________________ * PHPLIB braucht Zugriff auf ein Includeverzeichnis. Wenn man die Konfigurationsdirektive include_path nicht bestimmen kann, kann man stattdessen in prepend.php3 die Variable $_PHPLIB["libdir"] setzen und die Datei prepend.php3 mit vollem Pfad einbinden. Eine Seite, die PHPLIB verwendet, beginnt dann beispielweise folgendermaßen: _____________________________________________________________ _____________________________________________________________ In prepend.php3 steht dann beispielsweise: _____________________________________________________________ if (!is_array($_PHPLIB)) { $_PHPLIB["libdir"] = "/home/www/include"; } _____________________________________________________________ * PHPLIB braucht Zugriff auf ein auto_prepend_file . Wenn dies nicht gegeben ist, muß man am Anfang einer Seite die Datei prepend.php3 manuell einbinden, wie im vorhergehenden Stichpunkt gezeigt. 19.5. "Oops, php3_SetCookie called after header has been sent!" | [2116]PHPLIB | [2117]Fehler | [2118]Cookie | [2119]header | Antwort von [2120]Kristian Köhntopp PHP puffert seine Ausgaben nicht. Die Funktionen [2121]setcookie() und [2122]header() können nur verwendet werden, solange noch keine einzige Ausgabe durch das PHP-Script gemacht wurde. Fügt man bei der Bearbeitung der prepend.php3 oder local.inc Leerzeichen oder Leerzeilen an, kommt es bei der Einbindung dieser Dateien jedoch zur Ausgabe dieser Zeichen durch das PHP-Script, und damit sind diese beiden Funktionen nicht mehr durch PHPLIB verwendbar. Auch vor dem Aufruf von page_open() durch die eigentliche Seite dürfen keine Ausgaben gemacht werden. 19.6. GET-Mode oder Cookie-Mode? Sind Cookies böse? | [2123]PHPLIB | [2124]Cookie | [2125]GET | [2126]Methode | [2127]Modus | [2128]Session | [2129]SID | [2130]Propagation | [2131]Grundlage | Antwort von [2132]Kristian Köhntopp Jede Form von Session-Management basiert auf zwei grundlegenden Dingen: * Einer Session-ID, die von Seite zu Seite weitergegeben wird. * Einem Datensatz mit Variablen, der auf dem Server in einem dauerhaften Speicher verbleibt und der mit der Session-ID angesprochen wird. Grundsätzlich gibt es zwei Methoden, die Session-ID von einer Seite zu nächsten weiterzugeben: Entweder die ID wird per Cookie "unsichtbar" als Bestandteil des Requests weitergegeben, oder sie wird auf irgendeine Weise Bestandteil der URL, etwa als GET-Parameter mit einem ? an die URL angehängt, als PATH_INFO an die URL angehängt, als regulärer Pfadbestandteil, der von mod_rewrite herausgepult wird oder als Bestandteil des Hostnamens mit einem Wildcard A-Records im DNS. PHPLIB unterstützt direkt die Weitergabe der Session-ID als Cookie und als GET-Parameter, über mod_rewrite und einige minimale Änderungen ist jedoch auch die Weitergabe als Pfadbestandteil oder Hostname möglich. Ist die Session-ID in irgendeiner Form Bestandteil der URL, bekommt man das Problem, daß die URL mit der Session-ID gebookmarked wird oder - schlimmer - irgendwo abgedruckt wird. In diesem Fall kann es dazu kommen, daß zwei Benutzer dieselbe Session verwenden, die nichts miteinander zu tun haben. Dies kann bei Cookies niemals der Fall sein. Daher ist es aus technischer Sicht auf jeden Fall günstiger, Cookies zur Propagation der Session zu verwenden. Die Presse und schlecht informierte Verbraucherschützer haben Cookies jedoch einen schlechten Ruf eingebracht. Dort wird behauptet, Cookies seien üble Instrumente, um den Kunden zu tracken und sein Verhalten im Web auszuspionieren. Tatsächlich ist es so, daß man wiedererkennbare Benutzer und ihr Verhalten aufzeichnen und auswerten kann - ohne Wiedererkennung sind jedoch auch keine Warenkörbe, personalisierte Websites oder andere individuelle Services möglich. Andererseits schützt das Ablehnen eines Cookies auch nicht vor dem Tracking und dem folgenden Auswerten des Benutzerverhaltens: Wie oben gezeigt, kann man Session-IDs auch auf andere, schlechtere Weise als durch Cookies weiterverbreiten. Wenn es eine Website also darauf abgesehen hat, einen Benutzer zu tracken, dann hilft das Ablehnen des Cookies exakt gar nichts. Stattdessen ist es notwendig, den Kontakt zu dieser Website vollständig abzubrechen (dies ist insbesondere dann der Fall, wenn man sich nicht von einer Banneragentur wie DoubleClick ausspionieren lassen möchte. Ablehnen des Cookies nutzt auch hier nichts. Stattdessen muß man sich einen Proxy wie etwa JunkBuster installieren und auf diesem allen Datenverkehr in Richtung DoubleClick erden). Anders gesagt: nicht die Cookies sind böse, sondern das, was manche Firmen damit und jeder anderen Form von eindeutiger Identifikation anstellen. Um bei den Benutzern optimal zu funktionieren, die umsichtigerweise Cookies nicht abgeschaltet haben, aber einen sinnvollen Fallback in allen anderen Fällen zu bieten, gibt es in PHPLIB die Betriebsart fallback_mode . Ist die Variable mode in Session auf cookie gestellt, kann man durch Einstellen eines fallback_mode von get dafür Sorgen, daß PHPLIB zunächst einmal versucht, eine Session mit Cookies aufzubauen. Hat der Anwender jedoch Cookies abgeschaltet, wird transparent auf GET-Modus umgeschaltet. 19.7. Was ist das Sevenval-Patent? | [2133]Sevenval | [2134]SID | [2135]Session | [2136]Patent | [2137]Hostname | [2138]Propagation | Antwort von [2139]Kristian Köhntopp Die Firma [2140]Sevenval hat sich die Speicherung der Session-ID im Hostnamen patentieren lassen. Das Patent von Walkowiak, Olaf und Sponagl, Paul umfaßt laut Abstract folgendes: In a method for providing state information in a stateless data communications protocol, the state information being provided between a client and a server site, said server site being accessible at each of cluster of site names, one site name of a said cluster of site names is used for accessing said server site, said site name containing the encoded state information. A computer program product and an apparatus compromise corresponding features. The invention creates a way of providing state information in a stateless data communication protocol with very little effort. In der Patentschrift: ... merits of the inventors have shown that the configuration of both the nameserver and the server site in the way described are possible with very little programming effort. Im Wesentlichen versucht die Schrift, auf ca. 30 Seiten mit vielen unnötigen Worten anhand von Apache, Bind, PHP, mod_rewrite und mod_unique zu erklären, wie die Technik funktioniert - genauso, wie es jeder nach wenigen Minuten selber nachkonfiguriert hat. Klar ist, daß zwar die Idee, nicht aber die Konfiguration geschützt ist. Die Erfindung umfaßt, unter Benutzung obiger "said" Methode insbesondere: * Die Anwendung mit HTTP * Die Benutzung von Redirects * Die Verwendung mit IP-Nummern anstelle von Domain-Namen. Im anschließenden Gespräch ergaben sich noch folgende Infos: * Die Benutzung soll nur für den kommerziellen Gebrauch kostenpflichtig sein. Unis etc. sollen es so machen können. * Kosten sind angedacht in der Größenordnung von ca. 30.000 DM pro Server pro Jahr. * Es besteht Einigkeit darüber, daß das genannte Verfahren primär dem Designer nützt. 19.8. Warum verwendet PHPLIB nicht die IP-Nummer des Browsers als Schutz gegen eine Übernahme der Session? | [2141]PHPLIB | [2142]IP | [2143]Session | [2144]SID | [2145]Sicherheit | [2146]Proxy | Antwort von [2147]Kristian Köhntopp PHPLIB versucht, Sessions Benutzern zuzuordnen. IP-Nummern sind konstruktionsbedingt immer den Netzwerkinterfaces von Rechnern und nicht Benutzern zugeordnet. Das ist eine vollkommen andere Sache und die Auswertung von IP-Nummern würde zu Fehlern im Betrieb führen. * Ein Rechner kann mehr als einen Benutzer haben. Jeder Benutzer auf diesem Rechner würde mit derselben IP-Nummer arbeiten. Ein Beispiel ist der Rechner kruuna.helsinki.fi : _____________________________________________________________ kris@valiant:~ > finger @kruuna.helsinki.fi | wc -l 114 _____________________________________________________________ Auf diesem Rechner sind zum Meßzeitpunkt über 110 Benutzer angemeldet gewesen, die alle über dieselbe IP-Nummer arbeiten. * Ein Rechner kann mehr als einen Benutzer repräsentieren. Bei vielen Providern greifen Benutzer über Proxy-Server auf das Netz zu. Nach außen scheinen alle Zugriffe aus dem Netz des Providers von dem Proxy-Server zu kommen. Wenn der Proxy des Providers die Anonymisierungsfunktionen eingeschaltet hat, die z.B. der meistverwendete Proxy, squid2 , ab Werk mitbringt oder die Programme wie WebWasher und JunkBuster bieten, dann sind diese Anwender auch durch weitere Header nicht zu unterscheiden. * Die sichtbare IP-Nummer eines Benutzers kann während der Session wechseln. Viele Proxy-Server arbeiten in einem Cache-Verbund mit Lastverteilung. Die nach außen sichtbare IP-Nummer eines Anwenders wird je nach Lastsituation im Cache-Verbund diejenige IP-Nummer des am wenigsten ausgelasteten Proxy-Servers sein. * Die tatsächliche IP-Nummer eines Benutzers kann während der Session wechseln. Viele Anwender arbeiten mit Timeout bei Inaktivität und dynamisch zugeteilten IP-Nummern. Läßt ein Anwender seinen Browser einige Minuten ungenutzt stehen, wird sich die IP-Verbindung abbauen. Dem Anwender wird beim Neustart der Netzverbindung unter Umständen eine neue, andere IP-Nummer zugeteilt. Auch eine Auswertung der Headerzeile X-Forwarded-For , die manche Proxies setzen, ist nicht sinnvoll: * Diese Headerzeile kann gesetzt sein, muß jedoch nicht vorhanden sein. Squid, Webwasher und Junkbuster entfernen diese Headerzeile, wenn dies gewünscht wird. * Die Information in dieser Headerzeile ist weder authentisch noch eindeutig: Die verwendete IP-Nummer kann die IP-Nummer eines RFC-Netzes sein - es gibt also sehr viele Maschinen auf der Welt mit der IP-Nummer 192.168.1.1. 19.9. Warum sind die Session-IDs von PHPLIB so lang? | [2148]PHPLIB | [2149]SID | [2150]Session | [2151]Sicherheit | Antwort von [2152]Kristian Köhntopp PHPLIB verwendet als Session-ID eine kryptographische Prüfsumme (MD5-Verfahren) über einen String mit einer geheimen Komponente. Auf diese Weise ist die Session-ID einer PHPLIB-Session nicht vorhersagbar und ratbar. Würde PHPLIB fortlaufende Nummern als Session-IDs verwenden, könnte der Inhaber der Session 17 davon ausgehen, daß auch die IDs 16 und 18 belegt sind und versuchen, seine Session-ID zu ändern und so eine fremde Session zu übernehmen. Dadurch, daß PHPLIB quasi-zufällige, nicht ratbare IDs verwendet, ist diesem Angriff ein Riegel vorgeschoben. 19.10. Was schreibe ich denn nun in meine local.inc? | [2153]PHPLIB | [2154]Konfiguration | [2155]Installation | [2156]Datenbank | [2157]Anleitung | Antwort von [2158]Kristian Köhntopp In einer typischen Installation von PHPLIB wird keine der mitgelieferten Dateien verändert mit Ausnahme der Dateien prepend.php3 , local.inc und setup.inc . In der Datei prepend.php3 wird festgelegt, welche Dateien auf jeder PHPLIB nutzenden Seite per [2159]require() eingebunden werden. Standardmäßig wird ein MySQL-Datenbankinterface und die SQL-Storagemethode eingebunden. Speichert man seine Sessiondaten nicht in einer SQL-Datenbank oder verwendet man kein MySQL, so muß man diese Datei anpassen. In der Datei setup.inc stehen die Anweisungen, die bei der Initialisierung einer neuen Benutzersession ausgeführt werden, wenn in der Session-Definition die Option auto_init verwendet wird (siehe Dokumentation von PHPLIB). Dies ist ein fortgeschrittenes Feature und wird zunächst nicht benötigt. Es ist per Default ausgeschaltet. In der Datei local.inc findet die eigentliche Anpassung von PHPLIB statt. Die Klassendefinitionen von PHPLIB werden in der Regel nicht direkt verwendet, sondern man definiert sich eigene Klassen, die die Definitionen von PHPLIB erweitern und auf die eigenen Verhältnisse anpassen. Diese Anpassungen werden in der Regel in der Datei local.inc abgelegt. Um mit PHPLIB arbeiten zu können, muß man minimal eine Datenbankklasse als Unterklasse von DB_SQL definieren, eine Speichermethode als Unterklasse von z.B. CT_Sql definieren und die Eigenschaften der eigenen Session als Unterklasse von Session festlegen. Minimal muß eine local.inc also folgendermaßen aussehen: _________________________________________________________________ class DB_Beispiel extends DB_Sql { var $Host = "localhost"; # Name des Datenbankservers var $Database = "beispiel"; # Name der MySQL-Datenbank var $User = "web"; # Name des Datenbankusers var $Password = ""; # Paßwort des Datenbankusers var $Debug = 0; # SQL-Debugging ausgeschaltet } class Beispiel_CT_Sql extends CT_Sql { # Name der Datenbankklasse von oben var $database_class = "DB_Beispiel"; # Name der active_sessions-Tabelle var $database_table = "active_sessions"; } class Beispiel_Session extends Session { # Name der Klasse aus der Definition var $classname = "Beispiel_Session"; # Irgendein String, aber geheim muß er sein var $magic = "Hocuspocus"; # Verwende Cookies, um die Session weiterzugeben var $mode = "cookie"; # Verwende Sessioncookies # (Diese werden nicht in der cookies.txt gespeichert) var $lifetime = 0; # Name der CT_Sql-Unterklasse var $that_class = "Beispiel_CT_Sql"; # Garbage Collection mit 5% Wahrscheinlichkeit, var $gc_probability = 5; # Caching aller Seiten komplett verbieten var $allowcache = "no"; } _________________________________________________________________ 19.11. ERROR 1146: Table 'xyz.active_sessions' doesn't exist! | [2160]PHPLIB | [2161]Fehler | [2162]Datenbank | [2163]MySQL | [2164]Tabelle | [2165]Session | Antwort von [2166]Kristian Köhntopp PHP hat ein Misfeature mit dem Namen Connection reuse . Diese Eigenschaft bewirkt, daß man keine zwei Datenbankverbindungen mit denselben Connectparametern (in MySQL: Username, Paßwort, Host) öffnen kann. Versucht man eine zweite Verbindung mit denselben Eigenschaften zu öffnen, liefert PHP immer die erste Link-ID zurück. _________________________________________________________________ kris@valiant:~/www/kris.koehntopp.de/pages/php > ~/bin/php -q "Example_Session")) ?> "> "> _________________________________________________________________ 19.13. Internet Explorer: Meine Seiten werden nicht aktualisiert. | [2173]PHPLIB | [2174]IE | [2175]Cache | [2176]aktualisiert | [2177]Session | Antwort von [2178]Kristian Köhntopp Internet Explorer cached sehr aggressiv. Nur wenn man das Caching einer Seite vollständig verbietet, werden Seiten korrekt aktualisiert. Dazu ist in der eigenen Unterklasse von Session in der Datei local.inc die Option allowcache auf no zu stellen. _________________________________________________________________ class Example_Session extends Session { ... var $allowcache = "no"; ## ab Version 7.2c: "passive" ... } _________________________________________________________________ In PHPLIB 7.2c ist eine neue Einstellung passive für die Variable allowcache dazugekommen. In dieser Einstellung wird für den Netscape-Server das lokale Caching der Seiten erlaubt, für den Internet Explorer werden besondere Header gesendet, die diesem das Caching der Seite verbieten. Dies sollte in den meisten Fällen die Probleme lösen. Wo sie es nicht tun, muß weiterhin mit $allowcache = "no" gearbeitet werden. 19.14. Wie kann ich Reloads durch den User erkennen und verhindern? | [2179]PHPLIB | [2180]Reload | [2181]Formular | [2182]verhindern | [2183]Fehler | Antwort von [2184]Kristian Köhntopp Gewöhnlich macht man dies, indem man mit Session arbeitet und bei jedem Formular eine eindeutige ID "Challenge" als Hidden-Variable in das Formular mit aufnimmt, die man sich außerdem in einer lokalen Sessionvariablen auf dem Server merkt. Wenn das Formular abgesendet wird, vergleicht man die gelieferte Challenge mit der lokal gemerkten Challenge und akzeptiert das Formular nur dann, wenn beide übereinstimmen. Wenn das Formular verarbeitet wird, löscht man die Challenge in der Sessionvariablen nach Abschluß der Verarbeitung. Wird das Formular ein weiteres Mal versendet, liefert es die alte Challenge aus der Hidden-Variable, deren Gegenstück in der Sessionvariablen aber bereits gelöscht wurde. Man kann dies sehr schön mit einem generischen Formularvalidator automatisieren, dann hat man gar keine Arbeit mehr damit. 19.15. Wie kann ich meine Variablen initialisieren und registrieren? | [2185]PHPLIB | [2186]Session | [2187]Start | [2188]Registrierung | [2189]Initialisierung | Antwort von [2190]Kristian Köhntopp Wenn ein Benutzer zum ersten Mal Kontakt mit einer Webanwendung unter PHPLIB aufnimmt, dann kann dies auf einer beliebigen Seite geschehen - nicht notwendigerweise die Startseite der Anwendung. Eine Webanwendung muß jedoch zum korrekten Funktionieren eine Reihe von Variablen vorbelegen und diese beim Sessionmanagement registrieren. Für diesen und andere Zwecke (etwa: Sessionanalyse und Statistik) kennt die Session-Klasse von PHPLIB eine Variable auto_init . Diese Variable kann den Namen einer Include-Datei enthalten, die beim Start einer Session genau einmal geladen und ausgeführt wird. Per Konvention benennt man diese Datei setup.inc . Der Inhalt von setup.inc wird von innerhalb einer Funktion geladen und ausgeführt. Daher ist es notwendig, alle zu registrierenden und initialisierenden Variablen mit der Anweisung global zu importieren. Um eine Variable $s auf einen Startwert 17 zu setzen und beim Sessionmanagement zu registrieren, würde setup.inc das folgende Aussehen haben: _________________________________________________________________ register("s"); ?> _________________________________________________________________ 19.16. Wie kann ich auto_init benutzen, um Session-Statistiken zu erfassen? | [2191]PHPLIB | [2192]auto_init | [2193]Session | [2194]Statistik | [2195]IP | [2196]Browser | [2197]Referer | Antwort von [2198]Kristian Köhntopp Wenn man beim Start einer Session den Referer, die Adresse des Anwenders und seinen Browsertyp erfassen möchte, dann muß man eine Tabelle in der Datenbank erzeugen, die diese Daten speichern kann. Eine solche Tabelle kann zum Beispiel so aussehen: _________________________________________________________________ CREATE TABLE session_stats ( p_sid varchar(32) NOT NULL, p_name varchar(32) NOT NULL, p_start_time varchar(14) DEFAULT '' NOT NULL, p_referer varchar(250) NOT NULL, p_addr varchar(15) NOT NULL, p_user_agent varchar(250) NOT NULL, INDEX session_identifier (p_name, p_sid), INDEX start_time (p_start_time) ); _________________________________________________________________ In dieser Tabelle werden neben den genannten Angaben noch die Session-ID des Anwenders und die Startzeit der Session erfaßt. Dies geschieht mit Hilfe eines Datenbankobjektes und der folgenden setup.inc -Datei: _________________________________________________________________ name, $sess->id, $now, $HTTP_REFERER, $REMOTE_ADDR, $HTTP_USER_AGENT); # Query absenden $db->query($query); ?> _________________________________________________________________ Erfaßt man diese Daten z.B. in einem Webshop und merkt man sich auch die Session-ID und das Datum einer Bestellung, kann man die Anzahl der Sessions ermitteln, die zu einer Bestellung geführt haben sowie die Länge dieser Sessions. 19.17. Wie kann ich eine Datei mit einem Paßwort schützen? | [2199]PHPLIB | [2200]Login | [2201]Sicherheit | [2202]Datenbank | [2203]Beispiel | Antwort von [2204]Kristian Köhntopp Um eine Datei mit einem Paßwort schützen zu können, muß in der Datei local.inc eine eigene Unterklasse von Auth erzeugt werden, die den Benutzernamen und das Paßwort des Benutzers prüft. Falls der Benutzer korrekte Angaben gemacht hat, muß diese Klasse die Benutzer-ID ( uid ) des Benutzers zurückliefern, andernfalls false . Außerdem muß die Klasse Funktionen enthalten, die einen Loginbildschirm malen. PHPLIB kann gegen jede beliebige Datenbank mit Paßworten authentisieren, weil das Framework von PHPLIB keine Authentisierung selbst mitbringt. Es ist stattdessen die Aufgabe des PHPLIB-Anwenders, sich selbst eine solche Funktion zum Vergleichen von Paßworten zu schreiben. Zum Glück liefert PHPLIB in der standardmäßig gelieferten local.inc ein Beispiel für eine solche Funktion mit, die in diesem Beispiel gegen einen MySQL-Server authentisiert. In der Unterklasse von Auth muß der PHPLIB-Anwender zwei Funktionen schreiben: Die Funktion auth_loginform() muß ein Loginformular malen, und die Funktion auth_validatelogin() muß die Daten aus dem Loginformular übernehmen und dann gegen eine Paßwort-Datenbank prüfen. Der einfachste Fall sieht aus wie folgt: _________________________________________________________________ class Beispiel_Auth extends Auth { var $classname = "Beispiel_Auth"; var $lifetime = 15; var $userpass = array( "kris" => "test", "root" => "geheim" ); function auth_loginform() { global $sess; global $_PHPLIB; include($_PHPLIB["libdir"] . "loginform.ihtml"); } function auth_validatelogin() { global $username, $password; if ($this->userpass[$username] == $password) return $username; return false; } } _________________________________________________________________ Eine Anmeldung gegen diese Klasse gilt für 15 Minuten. Nach 15 Minuten ohne Seitenabruf ist der Benutzer automatisch ausgeloggt und muß sich erneut beim System anmelden. Dies wird durch die Variable $auth->lifetime bewirkt, die hier im Beispiel auf den Wert 15 gesetzt wird. Der Beispielcode oben verwendet die Daten aus der Datei loginform.ihtml , um einen Loginbildschirm zu zeichnen. Diese Datei kann nach den Bedürfnissen der Anwendung umgestaltet werden, oder der Code in der Funktion auth_loginform() kann komplett neu geschrieben werden und so beliebige Loginformulare erzeugen. Die Funktion auth_validatelogin() bindet die Formularvariablen username und password aus dem Loginformular mit Hilfe der Anweisung global ein, und prüft dann, ob der angegebene Benutzername in Verbindung mit dem Paßwort gültig ist. Dazu wird einfach in einem statischen Array nachgeschlagen, das zulässige Username-Paßwort Kombinationen auflistet. Ist das angegebene Paßwort korrekt, wird einfach der Benutzername als interne User-ID zurückgegeben (PHPLIB verläßt sich an anderer Stelle darauf, daß eine User-ID den Typ varchar(32) hat). Andernfalls wird false geliefert. Kompliziertere Vergleiche sind möglich. In der mitgelieferten local.inc zum Beispiel wird eine Anmeldung gegen eine MySQL-Datenbanktabelle durchgeführt. Die Logik hinter der Funktion ist jedoch dieselbe: Mit Hilfe des Benutzernamens und des Paßwortes wird in der Datenbanktabelle nachgeschlagen und eine User-ID oder false generiert. Auf einer Seite kann nun der folgende Code eingebunden werden: _________________________________________________________________ "Beispiel_Session", "auth" => "Beispiel_Auth" ); ?> Dieser Text ist nur sichtbar, wenn das Paßwort stimmt. _________________________________________________________________ Wieder wird die page_open() -Funktion verwendet, um PHPLIB die Namen der zu verwendenden Klassen bekannt zu machen. Außerdem ist wichtig, daß auf den Seiten page_close() aufgerufen wird, damit der Zähler für das automatische Timeout korrekt aktualisiert wird. 19.18. Wie kann ich mich gegen einen LDAP-Server authentisieren? | [2205]PHPLIB | [2206]LDAP | [2207]Passwort | Antwort von [2208]Kristian Köhntopp 19.19. Wie kann ich Zugriffsrechte in PHPLIB definieren? | [2209]PHPLIB | [2210]Recht | Antwort von [2211]Kristian Köhntopp 19.20. Wie kann ich einen Warenkorb realisieren? | [2212]PHPLIB | [2213]Shop | Antwort von [2214]Kristian Köhntopp 19.21. Wie kann ich eine Menünavigation erzeugen? | [2215]PHPLIB | [2216]Menue | Antwort von [2217]Kristian Köhntopp 19.22. Was sind Templates? Warum sind Templates nützlich? | [2218]PHPLIB | [2219]Template | [2220]Zweck | [2221]Trennung | [2222]Code | [2223]Design | [2224]Wartung | Antwort von [2225]Martin Jansen Templates bieten die Möglichkeit, Code und Design einer Seite sehr gut zu trennen. Beim Templateparsing wird eine reine HTML-Seite durch PHP analysiert und Platzhalter in der HTML-Seite durch Werte ersetzt, die PHP vorgibt (zum Beispiel mit Daten aus einer Datenbank). Nach der Analyse und dem Ersetzen der Platzhalter wird der geparste HTML-Code ausgegeben. Bei der Benutzung von Templates kann der Webdesigner in aller Ruhe seine HTML-Seiten inkl. aller Grafiken, Tabellen, Stylesheets etc. erstellen, ohne sich Gedanken über die PHP-Skripte machen zu müssen, die eingesetzt werden sollen. Der PHP-Programmierer kann sich parallel dazu ganz auf die Entwicklung der Skripte konzentrieren und muss nur dafür sorgen, daß die Daten bereit stehen, um in das Template integriert zu werden. Ein Beispiel für die Anwendung der Template-Klasse der PHPLIB: Die HTML-Datei, die der Webdesigner erstellt hat, könnte zum Beispiel so aussehen: _________________________________________________________________ Templates mit der PHPLIB

{UEBERSCHRIFT}

{TEXT}

_________________________________________________________________ Das dazu passende PHP-Skript sieht so aus: _________________________________________________________________ set_file(array("page" => "content.tpl")); /* Werte für Platzhalter festlegen */ $tpl->set_var(array("UEBERSCHRIFT" => $ueberschrift, "TEXT" => $text)); /* Template parsen */ $tpl->parse("OUT","page") /* geparstes Template ausgeben */ $tpl->p("OUT"); ?> _________________________________________________________________ 19.23. Was ist default Authentication? Warum ist das nützlich? | [2226]PHPLIB | [2227]Login | [2228]default | [2229]Authentication | [2230]Zweck | [2231]Benutzer | Antwort von [2232]Björn Schotte Mit der default Authentication wird erreicht, dass der Benutzer beim Aufrufen einer Seite als Benutzer "nobody" identifiziert wird. Damit ist es möglich, ganz normale Webseiten zu entwickeln, ohne dass der komplette Loginscreen aus loginform.ihtml erscheint. Ein Anwendungsbeispiel wären hier z.B. die diversen Freemailer wie GMX.de, die ganz normal ihre Seiten anzeigen, an der Seite aber ein Loginformular zur Verfügung stellen, mit dem man (im PHPLIB-Sinne gesprochen) seinen Benutzerstatus von Benutzer "nobody" auf seinen eigenen Login umbiegen und dann zum Beispiel zusätzliche, persönliche Informationen auf einer normalen Seite angezeigt bekommen kann. Damit die default Authentication wirksam wird, muss in der vererbten Auth-Klasse in local.inc einfach nur folgende Klassenvariable hinzugefügt werden: _________________________________________________________________ var $nobody = TRUE; _________________________________________________________________ Würde man nun eine Seite mit Autentifikation erstellen: _________________________________________________________________ "meineSession", "auth" => "meineAuth")); print "Sie haben die UID ".$auth->auth["uid"]; page_close(); ?> _________________________________________________________________ So würde diese Webseite ausgeben: "Sie haben die UID nobody" und kein Loginscreen würde gezeichnet werden. Um nun die Möglichkeit zu bieten, sich einloggen zu können, sind folgende Schritte nötig: die Auth-Klasse der PHPLIB kennt eine Methode auth_preauth(), die im Loginmechanismus als erstes aufgerufen wird und defaultmäßig ein return false; zurückliefert. Wenn auth_preauth() einen anderen Wert als FALSE, nämlich eine UserID (uid) zurückliefert, so wird der Loginmechanismus unterbrochen und ein Benutzer als autentifiziert markiert. Man kann sich also diese Methode zu Nutze machen und dafür sorgen, den Mechanismus mit Zeichnen des Loginscreens zu überspringen. Die Methode auth_preauth() wird also in der vererbten Klasse überschrieben und springt die Methode auth_validatelogin() direkt an: _________________________________________________________________ function auth_preauth() { return $this->auth_validatelogin(); } _________________________________________________________________ Dieser Code muß in Ihre vererbte Auth-Klasse eingefügt werden. Nun fehlen noch zwei kleine Schritte: Sie müssen auf Ihrer Seite ein Formular bauen und dafür sorgen, dass der Benutzer nach Abschicken des Formulars eingeloggt wird. Um letzteres zu erreichen, kennt PHPLIBs Auth-Klasse eine sehr nützliche Methode, auth_loginif(). Wenn dieser Methode ein Parameter mit Inhalt (egal, welcher) übergeben wird, so wird der komplette Autentifikationsmechanismus gestartet (und dadurch, dass Sie auth_preauth() definiert haben, das Zeichnen des Loginscreens übersprungen und stattdessen direkt auth_validatelogin() aufgerufen, die anhand der globalen Variablen $username und $password überprüft, ob die eingegebenen Daten richtig sind). Dies machen Sie sich zu Nutze: Sie schreiben direkt nach dem page_open() Aufruf folgendes: _________________________________________________________________ page_open( .... ); $auth->login_if($again); _________________________________________________________________ $again ist eine Variable, die als hidden-Element über das Eingabeformular mitgeschickt wird und einen beliebigen Wert (z.B. "yes") enthält. Das Eingabeformular könnte so aussehen: _________________________________________________________________
Username:
Passwort:
_________________________________________________________________ Es ruft also sich selbst auf und übermittelt die Variablen $again (mit dem Inhalt "yes") und $username sowie $password. Nach Aufruf von sich selbst wird nach obigem page_open() Call die Methode login_if() aufgerufen. Da $again nun durch das Formular gesetzt ist, tritt der Loginmechanismus in Gang. Da die Methode auth_preauth() von Ihnen geändert wurde und direkt auth_validatelogin() anspringt, überprüft auth_validatelogin() die globalen Variablen $username und $password (die wiederum vom gerade abgeschickten Formular übermittelt wurden) und loggt den Benutzer nach korrekter Eingabe ein. Jetzt haben Sie also einen eingeloggten Benutzer auf einer normalen Seite. Um ihm nun persönliche Informationen anzeigen zu können, überprüfen Sie einfach, ob $auth->auth["uid"] einen anderen Wert als "nobody" enthält. Wenn ja, wissen Sie, dass Sie es mit einem eingeloggten Benutzer zu tun haben und können ihm z.B. seine aktuellen Termine oder Ähnliches anzeigen. 20. Webserver und PHP 20.1. [2233]Apache: Kann ich PHP auch auf .html-Dateien anwenden? 20.2. [2234]Apache: Wie kann ich ein Verzeichnis mit einem Paßwort schützen? 20.3. [2235]Apache: Wie kann ich ein Verzeichnis mit PHP mit einem Paßwort schützen? 20.4. [2236]Kann ich mit CGI PHP ein Verzeichnis mit einem Paßwort schützen? 20.5. [2237]Wie kann ich mit PHP die Bildschirmauflösung des Browsers herausfinden? 20.6. [2238]Wie kann ich das Caching einer Seite verhindern? 20.7. [2239]"Document contains no data" 20.8. [2240]Wie erzeuge ich mit PHP einen Redirect auf eine andere Seite? 20.9. [2241]Was sind Sessions und warum sind sie nützlich? 20.10. [2242]Wie kann ich mit PHP WAP-Seiten erzeugen? 20.1. Apache: Kann ich PHP auch auf .html-Dateien anwenden? | [2243]phps | [2244]html | [2245]Syntax | [2246]Highlighting | [2247]Parser | [2248]Apache | [2249]Datei | [2250]Endung | Antwort von [2251]Johannes Frömter Prinzipiell kann man Dateien mit beliebigen Endungen durch PHP verarbeiten lassen, also auch die Endung .html . Normalerweise braucht man hierzu Zugriff auf die Apache-Konfigurations-Datei httpd.conf (wenn der Provider es nicht unterbunden hat, reicht aber auch ein Eintrag in einer .htaccess -Datei, die man einfach in sein Home-Directory stellt). Auf diese Weise lassen sich auch prima in PHP3 geschriebene Anwendungen bei einem Provider betreiben, der nur noch PHP4 anbietet (die Kompatibilität beträgt praktisch 100%, siehe [2252]Anhang B. des PHP Manuals ). Je nachdem, ob PHP in der Modul- oder der CGI-Version zum Einsatz kommt, unterscheiden sich die Konfigurationsanweisungen. Bei PHP4 ist das Modul die häufigere Art, hier sieht die Zeile meist so aus: _________________________________________________________________ AddType application/x-httpd-php .php .php4 .html _________________________________________________________________ Mehrere Dateiendungen schreibt man einfach durch Leerzeichen getrennt hintereinander. Beim Massenhoster 1&1 (PureTec, S+P, sowie deren Reseller) ist bei AddType eine etwas andere Syntax notwendig: AddType x-mapp-php4 .php .html Diese Konfigurationsanweisungen können in der httpd.conf entweder in Section 2 ('Main' server configuration) stehen, in -Containern (Section 3), wo sie nur für den entsprechenden Domain-/Hostnamen gelten, oder in -Containern, wo sie nur für ein bestimmtes Verzeichnis und dessen Unterverzeichnisse gelten. Auch in .htaccess -Dateien sind sie möglich, sie gelten dann ebenfalls für nur für ein Verzeichnis bzw. dessen Unterverzeichnisse. Betreibt man PHP nicht als Webserver-Modul, sondern als CGI (z.B. PHP3 parallel zu PHP4), sieht der Eintrag folgendermaßen aus: _________________________________________________________________ AddType application/x-httpd-php3 .php3 .html Action application/x-httpd-php3 /cgi-bin/php // Für Windows ScriptAlias /php3/ "/path-to-php-dir/" AddType application/x-httpd-php3 .php3 Action application/x-httpd-php3 "/php3/php.exe" _________________________________________________________________ Solange die Modulversion von PHP verwendet wird und in den verarbeiteten Dateien keine PHP PIs (SGML Processing Instructions, die PHP einschalten: htpasswd -c etc/htpasswd kris New password: test Re-type new password: test Adding password for user kris kris@valiant:~/www/kris.koehntopp.de > htpasswd etc/htpasswd marit New password: test Re-type new password: test Adding password for user marit kris@valiant:~/www/kris.koehntopp.de > echo "users: kris marit" >> etc/htgroup _________________________________________________________________ Die Option -c steht dabei für create ; die Paßwortdatei wird neu angelegt. Ohne die Option werden Benutzer zu einer existierenden Paßwortdatei zugefügt bzw. die Paßworte existierender Benutzer werden geändert. Im Beispiel wird weiterhin eine Gruppe users angelegt, der die Benutzer kris und marit angehören. Ein Verzeichnis kann man nun durch das Anlegen einer .htaccess -Datei schützen (dazu muß AllowOverride AuthConfig in der httpd.conf für dieses Verzeichnis gesetzt sein) oder indem man die entsprechenden Konfigurationsanweisungen direkt in einen -Block für dieses Verzeichnis in die httpd.conf einsetzt. Im einfachsten Fall sind dies die Anweisungen _________________________________________________________________ AuthType Basic AuthName MyRealm AuthUserFile /home/www/servers/www.koehntopp.de/etc/htpasswd AuthGroupFile /home/www/servers/www.koehntopp.de/etc/htgroup require valid-user _________________________________________________________________ Die Anweisung AuthType legt fest, auf welche Weise der Browser das Paßwort übermittelt - bei der sehr unsicheren Basic-Authentication wird es nahezu im Klartext übertragen. Für den zu schützenden Bereich muß mit AuthName ein Name festgelegt werden. Die Direktiven AuthUserFile/ und AuthGroupFile legen die Datenquellen für die Authentisierung fest. Damit ein Anwender den geschützten Bereich betreten kann, muß er die in der require -Anweisung geforderten Bedingungen erfüllen. Im einfachsten Fall muß er einfach nur in der htpasswd -Datei stehen und das passende Paßwort wissen. Dies ist der Fall bei require valid-user . Mit komplizierteren Anweisungen kann man den Zutritt auch noch auf bestimmte Gruppen ( require group users ) oder auf bestimmte Benutzer ( require user kk ) einschränken. In CGI PHP hat man in einem auf diese Weise geschützten Bereich Zugriff auf den Benutzernamen in der Variablen $REMOTE_USER und die verwendete Authentisierungsmethode in $AUTH_TYPE . _________________________________________________________________ \n"; echo "REMOTE_USER = $REMOTE_USER
\n"; ?> _________________________________________________________________ Im Apache-Modul hat man in einem geschützten Bereich außerdem Zugriff auf die vollen Authentisierungsdaten. _________________________________________________________________ \n"; echo "REMOTE_USER = $REMOTE_USER
\n"; $a = getallheaders(); $au = split(" ", $a["Authorization"], 2); list($u, $p) = split(":", base64_decode($au[1])); echo "Decodiert zu User $u, Password $p
\n"; _________________________________________________________________ 20.3. Apache: Wie kann ich ein Verzeichnis mit PHP mit einem Paßwort schützen? | [2265]Passwort | [2266]schuetzen | [2267]Authentifizierung | [2268]Apache | [2269]401 | [2270]Verzeichnis | [2271]Login | Antwort von [2272]Kristian Köhntopp Dies ist in englischer Sprache ausführlich im [2273]PHP Handbuch beschrieben. Das Feature steht nur dann zur Verfügung, wenn PHP als Apache-Modul betrieben wird. _________________________________________________________________ "; echo "You entered $PHP_AUTH_PW as your password.

"; } ?> _________________________________________________________________ Nur wenn PHP selbst die Authentisierung vornimmt, stehen die Variablen PHP_AUTH_USER und PHP_AUTH_PW zur Verfügung. Sie können verwendet werden, um den Benutzer in einer Datei, einer Datenbank oder einer anderen Datenquelle nachzuschlagen und das Paßwort zu überprüfen. 20.4. Kann ich mit CGI PHP ein Verzeichnis mit einem Paßwort schützen? | [2274]CGI | [2275]Passwort | [2276]schuetzen | [2277]Authentifizierung | [2278]Verzeichnis | [2279]Login | Antwort von [2280]Kristian Köhntopp Nicht mit den Bordmitteln von PHP. Zwar kann man durch Senden eines Status -Header die Authentisierung auslösen, aber PHP übermittelt nicht die notwendigen Variablen an das Script zurück, wenn der Benutzer sich angemeldet hat. Stattdessen sollte man [2281]PHPLIB und das Auth -Objekt verwenden. 20.5. Wie kann ich mit PHP die Bildschirmauflösung des Browsers herausfinden? | [2282]Aufloesung | [2283]Browser | [2284]Client | [2285]Fenster | [2286]Bildschirm | Antwort von [2287]Kristian Köhntopp Das geht im allgemeinen Fall nicht. PHP wird auf dem Server ausgeführt, nicht im Browser des Zielsystems. Falls der Browser des Zielsystems JavaScript kann, falls dieser Browser JavaScript nicht disabled hat, falls die Firewall auf dem Weg zum Zielsystem nicht JavaScript ausfiltert und falls man an geeigneter Stelle ein Formular statt eines Links verwendet, dann kannst man in diesem Formular die Auflösung des Zielsystem durch JavaScript ermitteln lassen und an seine Site zurücksenden lassen. Mit den übrigen Fällen (Auflösung des Zielsystems nicht bekannt) muß man dennoch fertig werden. Die Tatsache, daß die Bildschirmauflösung des Zielsystems bekannt ist, hilft natürlich nicht beim Design, solange man nicht auch weiß * wie groß die verwendete Schrift ist, * ob die verwendeten Zeichensätze auf dem Zielsystem zur Verfügung stehen, * wie groß die Fenster auf dem Zielsystem zur Zeit gerade sind. Man kann aus diesen Gründen davon ausgehen, daß eine auflösungsabhänige Darstellung auch dann auf mehr als der Hälfte der Zielsysteme nicht korrekt gerendert werden kann, auch wenn die Bildschirmauflösung des Zielsystems bekannt ist. 20.6. Wie kann ich das Caching einer Seite verhindern? | [2288]Cache | [2289]Reload | [2290]Lebensdauer | [2291]Browser | Antwort von [2292]Kristian Köhntopp In HTTP 1.0 kann man das Caching einer Seite nur unvollständig steuern. Die ersten Webcaches haben die Lebensdauer von Seiten im Cache auf der Grundlage des Erzeugungsdatums geschätzt oder, wenn ein Expires -Header angegeben, diesen beachtet. Später ist der spezielle Header Pragma: no-cache eingeführt worden, um das Caching von Seiten durch Webcaches und Browser zu verbieten. Erst mit HTTP 1.1 kann eine spezielle Cache-Steuerung hinzu, die zwischen privaten (browsereigenen) Caches und öffentlichen Caches unterschied. Über den besonderen Header Cache-Control kann man die Lebensdauer von Seiten in Caches steuern. Microsoft kocht zusätzlich noch eine Spezialsuppe, indem sie für den MSIE 5.x spezielle [2293]Cache-Control Extensions definieren. Um das Caching einer Seite zu erlauben, kann man den folgenden Code verwenden: _________________________________________________________________ $expire = 15; # Lebensdauer der Seite im Cache in Minuten $exp_gmt = gmdate("D, d M Y H:i:s", time() + $expire * 60) ." GMT"; $mod_gmt = gmdate("D, d M Y H:i:s", getlastmod()) ." GMT"; # HTTP 1.0 header("Expires: " . $exp_gmt); header("Last-Modified: " . $mod_gmt); # HTTP 1.1 header("Cache-Control: public, max-age=" . $expire * 60); _________________________________________________________________ Um das Caching einer Seite auf private Caches zu begrenzen muß man Code wie diesen nehmen: _________________________________________________________________ $expire = 15; # Lebensdauer der Seite im Cache in Minuten $mod_gmt = gmdate("D, d M Y H:i:s", getlastmod()) ." GMT"; # HTTP 1.0 kennt keine privaten Caches, also nix cachen header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); header("Last-Modified: " . $mod_gmt); # HTTP 1.1 header("Cache-Control: private, max-age=" . $expire * 60); # MSIE 5.x special header("Cache-Control: pre-check=" . $expire * 60); _________________________________________________________________ Um das Caching einer Seite zu verhindern, ist der folgende Code passend: _________________________________________________________________ header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); header("Last-Modified: " . gmdate("D, d M Y H:i:s") ." GMT"); header("Cache-Control: no-cache"); header("Pragma: no-cache"); header("Cache-Control: post-check=0, pre-check=0"); _________________________________________________________________ Ein anderer Trick, mit dem man das Caching einer Seite gut verhindern kann, ist das Anhängen von Parametern an die URL einer Seite in der Form http://www.meinserver.de/bla.php?x=y oder das Einfügen von benutzerspezifischen Komponenten in die URL (in den Hostnamen, den Pfad oder den Dateinamen). 20.7. "Document contains no data" | [2294]leer | [2295]Seite | [2296]Dokument | [2297]Daten | [2298]Browser | [2299]Fehler | Antwort von [2300]Kristian Köhntopp Dies ist eine Netscape-Fehlermeldung, die dann auftritt, wenn der Webserver als Antwort auf einen Request genau null Bytes Antwort liefert. Wenn dies geschieht und die Seite eigentlich eine Ausgabe erzeugen sollte, dann hat möglicherweise der PHP-Interpreter ein Problem und stürzt mit einem Coredump ab. In diesem Fall sollte man versuchen, den Fehler zu reproduzieren und das kürzestmögliche (!) Script, das den Fehler erzeugt, in den Bugreport mit einbinden. 20.8. Wie erzeuge ich mit PHP einen Redirect auf eine andere Seite? | [2301]Redirect | [2302]Header | [2303]302 | [2304]HTTP | [2305]URL | Antwort von [2306]Kristian Köhntopp Um einen Redirect zu erzeugen, muß man den HTTP-Header Location senden und dort die neue URL angeben. Zum Senden von HTTP-Headerzeilen verwendet man die PHP-Funktion [2307]header() . Diese Funktion kann nur dann verwendet werden, wenn PHP noch keinen HTTP-Body ausgegeben hat, wenn also weder Fehlermeldungen, Leerzeilen, Leerzeichen noch HTML ausgegeben worden sind. _________________________________________________________________ # Redirect-Ziel header("Location: http://www.ziel.de/zielseite.html"); _________________________________________________________________ Wichtig: [2308]RFC 2616 schreibt im Abschnitt 14.30 Location eine sog. absoluteURI vor, d.h. die Adresse muss mit http:// beginnen, relative Anweisungen à la "Location: index.html" sind nicht standardkonform! Manche Browser sind zwar so tolerant, relative Angaben zu verstehen und in der Lage, selbständig die absolute Adresse zu ermitteln, aber verlassen kann man sich darauf nicht; die PHP-Funktion [2309]fsockopen() z.B. scheitert an derart ungültigen Location-Angaben. 20.9. Was sind Sessions und warum sind sie nützlich? | [2310]Session | [2311]GET | [2312]POST | [2313]PHPLIB | [2314]Sicherheit | [2315]Variablen | [2316]weitergeben | [2317]speichern | Antwort von [2318]Kristian Köhntopp Bei einer Session wird jedem Browser, der auf eine Webanwendung zugreift, eine Kennnummer gegeben, mit der man folgende Zugriffe dieses Browsers wiedererkennen kann. Auf dem Webserver werden unter dieser Kennnummer eine Reihe von PHP-Variablen gespeichert, die auf diese Weise von Seite zu Seite weitergereicht werden. Man erzielt damit einen ähnlichen Effekt wie mit -Variablen, die von Seite zu Seite weitergereicht werden, vermeidet jedoch eine Reihe von Nachteilen dieser Variablen: * Sessionvariablen sind in der Größe nicht durch etwa die Länge der URL oder andere Faktoren beschränkt. * Sessionvariablen werden nicht ständig zwischen dem Browser und dem Webserver hin- und hergebounced und sind daher der Manipulation eines Anwenders entzogen, sobald sie erst einmal Bestandteil der Session sind. * Sessionvariablen erlauben die Entwicklung weitergehender Nutzanwendungen wie zum Beispiel Benutzeranmeldungen, Warenkörbe oder andere Dienste, die einen Zustand bewahren müssen. Eine Webanwendung muß man sich aus zwei getrennten Teilen bestehend vorstellen. Der eine Teil, bestehend aus dem Webserver und allen anderen involvierten Rechnern auf dieser Seite der Firewall, ist grundsätzlich vertrauenswürdig. Daten, die von solchen Maschinen kommen, sind Bestandteil des durch den Serveradministrator kontrollierten Bereiches und daher mit großer Wahrscheinlichkeit korrekt und nicht kompromittiert. Der andere Teil ist alles jenseits der Firewall, einschließlich des Browsers des Benutzers. Daten, die von dort kommen, sind nicht vertrauenswürdig: Bei konventionellen Anwendungen werden mit jedem GET- oder POST-Request Parameter an den Server gesendet. Es handelt sich um sichtbare Formulardaten oder versteckte Formulardaten aus HIDDEN-Feldern oder statisch an URLs angehängte GET-Parameter sowie um Cookies. Diese Daten muß der Server jedes Mal wenn er sie erhält validieren und gegebenenfalls ablehnen. Das ist sehr, sehr schwierig zu machen und die meisten Anwendungen machen es nicht korrekt. Sie haben daher scheunentorgroße Zugangslöcher, die ein Anwender zum Hijacken der gesamten Webservermaschine benutzen kann. Diese Maschine wird dann zum Einfallstor für das gesamte Subnetz, in dem sich diese Maschine physikalisch befindet. Bei Anwendungen mit Sessions wird bei jedem Request die Session-ID gesendet und diese hat eine Form, die sie schwer manipulierbar macht. Manipuliert der Benutzer die Session-ID, so erwischt er mit an Sicherheit grenzender Wahrscheinlichkeit eine ungültige Session-ID und startet so eine neue Session. Die Anwendung initialisiert sich dann korrekt und der User startet, als hätte er die Anwendung soeben frisch aufgerufen. Manipulation der Session-ID wird also von der Anwendung erkannt und führt zu einem definierten und unschädlichen Verhalten. Außerdem erhalten auch Anwendungen mit Sessions ebenfalls Daten aus Formularen. In der Regel sind dies keine HIDDEN-Felder, denn der Zustand wird nun auf dem Server als Teil der Session gehalten. Stattdessen handelt es sich ausschließlich um sichtbare Formulardaten. Diese müssen beim Übergang aus dem Kontrollbereich des Benutzers in die Session natürlich validiert werden. Danach verbleiben sie jedoch in der Session und können als vertrauenswürdig angesehen werden. Vertrauen bedeutet in diesem Kontext, daß die Daten mindestens die zugesicherten Eigenschaften haben, auf die bei der Validierung getestet worden ist. Die Anwendung darf natürlich nicht darauf vertrauen, daß die Daten Eigenschaften haben, die nicht validiert worden sind. Wenn man bei der Übernahme von Formulardaten in Sessiondaten einen Benutzernamen auf \w{3,8} testet, dann kann man in der Anwendung zwar darauf vertrauen, daß der Benutzername keine bösen Sonderzeichen enthält, nicht leer ist und in die Datenbankfelder paßt, aber nicht darauf vertrauen, daß der Benutzername auch die Eigenschaft "eindeutig" und "noch nicht vergeben" erfüllt. In PHP3 lassen sich Sessions mit Hilfe von [2319]PHPLIB erzeugen. In PHP4 kann man alternativ auch die eingebauten [2320]Sessions der Sprache verwenden. 20.10. Wie kann ich mit PHP WAP-Seiten erzeugen? | [2321]WAP | [2322]WML | [2323]Header | [2324]Content-Type | Antwort von [2325]Kristian Köhntopp Die Seite muß den korrekten Content-Type bekommen. Dies erreicht man, indem man sein Script mit einer passenden [2326]header() -Anweisung beginnen läßt. _________________________________________________________________ header("Content-Type: application/vnd.wap.wml"); _________________________________________________________________ 21. Content Management Systeme 21.1. [2327]Was ist ein Content Management System? Warum ist es nützlich? 21.2. [2328]Welche PHP-basierten Content Management Systeme gibt es? 21.1. Was ist ein Content Management System? Warum ist es nützlich? | [2329]Template | [2330]Navigation | [2331]Zugriffskontrolle | Antwort von [2332]Björn Schotte Laut [2333]ContentManager ist ein Content Management System ein "Softwaresystem für das Administrieren von Webinhalten mit Unterstützung des Erstellungsprozesses basierend auf der Trennung von Inhalten und Struktur". Bei sehr vielen Websites kommt es nicht darauf an, dass man besonders tolle PHP-Applikationen erstellt. Viel wichtiger ist, dass man das Tagesgeschäft erledigen kann, ohne durch fehlerhafte Programme, umständliche Bedienungen, Heranziehen von Softwareentwicklern vom normalen Ablauf gestört zu werden. Ein CMS unterstützt dieses Vorhaben, indem es eine Website in mehrere Bereiche aufteilt und über eine (meist webbasierte) Oberfläche den einzelnen Mitarbeitern zugänglich macht. Das ermöglicht es auch nicht mit HTML versierten Mitarbeitern, Inhalte der Website zu pflegen. Den Rest, also die Integration des Inhalts in die Struktur, erledigt das CMS. Ein anderer Mitarbeiter, der zum Beispiel in HTML sehr fit ist, wird Zugriff auf das Layoutmodul des CMS haben und dort sogenannte HTML-Templates pflegen. In vielen CMSen sind diese Templates normaler HTML-Code, bei dem durch Schlüsselwörter definiert wird, an welcher Stelle welcher Inhalt gesetzt werden soll. Sehr nützlich bei einem CMS sind auch noch die verschiedenen Zugriffsrechte für einzelne Benutzer(gruppen). Das macht eine Kontrolle möglich, zum Beispiel das die Sekretärin nur im Inhaltsbereich Daten eingeben darf, aber keinen Zugriff auf das Layoutmodul des CMS hat. Der Nutzer, der die Website oder Teile davon pflegt, kommt also gar nicht mehr in Kontakt mit z.B. FTP-Programmen. Scheinbar komplizierte Technik wird in eine übersichtliche Oberfläche verpackt, damit auch weniger versierte Nutzer die Inhalte pflegen können. Module wie zum Beispiel Mediendatenbanken, die Bilder, Sounds, Dateien etc. verwalten, machen eine Pflege selbst komplexer Sites zum Kinderspiel. 21.2. Welche PHP-basierten Content Management Systeme gibt es? Antwort von [2334]Björn Schotte Systeme, die kostenlos zur Verfügung stehen: * [2335]Binarycloud * [2336]Contenido * [2337]Drupal * [2338]Midgard * [2339]phpCMS * [2340]Typo3 * [2341]Xinity Kommerziell erhältliche Systeme: * [2342]AMAN_RedSYS * [2343]contudo * [2344]datenKÜCHE * [2345]ireds * [2346]MIS Content-Management * [2347]omeco webcontent * [2348]PowerSlave * [2349]SixCMS 22. Häufig nachgefragte Standardscripte 22.1. [2350]Wo finde ich ein Script, das "xyz" kann? 22.2. [2351]Wie kann ich eine schummelsichere Abstimmung codieren? 22.3. [2352]Wie kann ich einen HTTP POST-Request absenden? 22.4. [2353]Wie kann ich einen HTTP POST-Request mit Datei-Upload absenden? 22.5. [2354]Wie kann ich eine Volltextsuche realisieren? 22.6. [2355]Wie kann ich mit PHP News lesen und schreiben? 22.7. [2356]Wie kann ich einen Onlineshop mit PHP realisieren? 22.8. [2357]Wie kann ich die IP des Users erfahren? 22.9. [2358]Wie kann ich ein JPEG-Bild verkleinern? 22.10. [2359]Wie kann ich die Performance zweier Befehle vergleichen? 22.11. [2360]Wie kann ich den Inhalt eines Verzeichnisses samt dem Inhalt aller Unterverzeichnisse ausgeben? 22.12. [2361]Wie kann ich aus einem Zahlenbereich von x bis y, zufällig n Zahlen auswählen, so daß keine Zahl doppelt vorkommt? 22.13. [2362]Wie kann ich zählen, wie oft auf einen Link geklickt wurde? 22.14. [2363]Wie kann ich das Datum der letzten Änderung einer Datei erfahren? 22.15. [2364]Welche in PHP realisierte Foren gibt es? 22.16. [2365]Wie biete ich meine Seiten mehrsprachig an? 22.17. [2366]Wie kann ich ermitteln, wieviele Besucher gerade meine Seite betrachten? 22.18. [2367]Welche Webmail-Oberflächen in PHP gibt es? 22.19. [2368]Wie überprüfe ich Hyperlinks auf ihre Gültigkeit? 22.20. [2369]Wie erzeuge ich Excel-Dateien mit PHP? 22.21. [2370]Wie kann ich prüfen, ob eine IP-Adresse in einem bestimmten Bereich liegt? 22.22. [2371]Wie kann ich prüfen, ob eine bestimmte ICQ UIN online ist? 22.23. [2372]Welche kostenlose Portalsoftware in PHP gibt es? 22.24. [2373]Welche Lösungen gibt es, um Diagramme zu erstellen? 22.25. [2374]Wie überprüfe ich, ob eine Zahl gerade oder ungerade ist? 22.26. [2375]Wie kann ich ID3-Tags von MP3-Dateien lesen/schreiben? 22.27. [2376]Wie kann ich eine whois-Abfrage mit PHP realisieren? 22.1. Wo finde ich ein Script, das "xyz" kann? | [2377]Script | [2378]fertig | [2379]Sammlung | [2380]suchen | [2381]finden | Antwort von [2382]Martin Jansen Es gibt im Internet eine Vielzahl von Seiten, die Scripte kostenlos zum Download anbieten, die viele verschiedene Zwecke erfüllen. Typische Anlaufstellen bei der Suche nach fertigen Scripten sind: * [2383]HotScripts * [2384]PHPBuilder.com - Snippet Library * [2385]PX: PHP Code Exchange * [2386]WeberDev * [2387]Zend Code Gallery Ebenso ist es hilfreich, bei [2388]Freshmeat nach php zu suchen, dort finden sich sehr viele PHP-Scripte. Eine weitere Möglichkeit, fertige PHP-Scripte zu finden, ist die Suche bei [2389]SourceForge nach php. 22.2. Wie kann ich eine schummelsichere Abstimmung codieren? | [2390]Abstimmung | [2391]Vote | [2392]Poll | [2393]Wahl | [2394]Umfrage | Antwort von [2395]Kristian Köhntopp Man kann keine schummelsichere Abstimmung im Web realisieren, die nicht unfair berechtigte Abstimmende ausschließt, außer man treibt sehr großen Aufwand. Typische Schutzmechanismen, die jedoch nur Pseudosicherheit geben, sind: Die Seite nimmt Parameter per POST, prüft den Referer und versucht einen Cookie zu setzen. Wer den Cookie hat, kann nicht mehr abstimmen. Zum Mogeln muß man der Seite also die richtigen Parameter aus dem Formular mit einem POST füttern, darf den Referer nicht vergessen und darf den Cookie nicht annehmen. Das war alles. Eine etwas kompliziertere Absicherung wäre ein Formular, das als hidden -Parameter eine Challenge hat, die auch auf dem Server als Session-Variable verbleibt. Ein Abstimmungsergebnis wird dann für die aktuelle Session nur angenommen, wenn die richtige Challenge mit in den Parametern ist. Das macht das Schummeln per Script ein wenig schwieriger, aber nicht unmöglich (mit LWP in Perl oder libwww in C problemlos möglich, mit PHP ein Code mit den preg_*-Funktionen. Mit SSL-Browser-Zertifikaten auf der Clientseite wäre es noch schwieriger zu fälschen, aber da gelangt man langsam in Regionen, wo es für den Wahlveranstalter wirklich teuer wird. Auf der Basis der IP-Nummern kann man nicht arbeiten, da viele Benutzer dieselbe IP-Nummer zu haben scheinen, wenn sie über Proxy-Server hereinkommen oder Benutzer auf einem Mehrbenutzer-Rechner sind (unfairer Ausschluß). Andererseits ist es für einen geübten Hacker oder einen Provider sehr leicht, von einer ganzen Reihe unterschiedlicher IP-Nummern mehrfach abzustimmen. 22.3. Wie kann ich einen HTTP POST-Request absenden? | [2396]POST | [2397]Request | [2398]Methode | Antwort von [2399]Kristian Köhntopp Das Script muß einen Socket mit der Funktion [2400]fsockopen() zum Zielserver öffnen und auf diesem Socket dann einen HTTP POST-Request simulieren. Anbei ein vollständiges Beispiel, das mit CGI-PHP auf der Unix-Kommandozeile verwendet werden kann. Das Script fälscht Einträge in einer Abstimmung auf dem Host [2401]www.linux.com , wo es für PHP als beste Scriptsprache stimmt. _________________________________________________________________ #! ./php -q $val) { $ds =sprintf("%s\nContent-Disposition: form-data; name=\"%s\"\n \n%s\n",$bo,$key,$val); $dc += strlen($ds); } $dc += strlen($bo)+3; fputs($fp, "Content-length: $dc \n"); fputs($fp, "\n"); foreach($data_to_send as $key=>$val) { $ds =sprintf("%s\nContent-Disposition: form-data; name=\"%s\"\n \n%s\n",$bo,$key,$val); fputs($fp, $ds ); } $ds = $bo."--\n" ; fputs($fp, $ds); while(!feof($fp)) { $res .= fread($fp,1); } fclose($fp); return $res; } # Fuer eine ASCII-Datei kann man es so machen # bei bin-Daten ueber fread gehen $fa = @file("http://www.dkfz.de/spec/fix/file.pdb"); # Konkretes Beispiel - Eine Chemie-Datei $xf="Content-Type: chemical/x-pdb\n\n".implode("",$fa); $data["disk"] = "on"; $data["file\"; filename=\"irgendwas.pdb"] = $xf; $data["smiles"] = ""; $data["hadd"] = "add"; $data["aroresolver"] = "on"; $data["format"] = "gif"; $data["interlace"] = "1"; $data["width"] = "600"; $data["height"] = "400"; $data["atomcolor"] = "Black"; $data["asymbol"] = "xsymbol"; $data["hcolor"] = ""; $data["csymbol"] = "special"; $data["hsymbol"] = "special"; $data["bondcolor"] = "Black"; $data["bgcolor"] = "White"; $data["border"] = "12"; $data["bonds"] = "8"; $data["wedges"] = "1"; $data["dashes"] = "1"; $data["crop"] = "2"; $data["align"] = "none"; $data["coord"] = "0"; $data["imagemap"] = "none"; $data["headercolor"] = "Black"; $data["header"] = ""; $data["footercolor"] = "Black"; $data["footer"] = ""; $data["commenttype"] = "none"; $data["comment"] = ""; $data["structure"] = "none"; $x = PostToHost( "www2.chemie.uni-erlangen.de", 80, "/cgi-bin/ services/gifcreator.tcl", "http://www2.chemie.uni-erlangen.de/services/gifcreat or/index.html", $data); # Diesen Teil kann man bestimmt noch optimieren ;-) $fp=fopen("struktur.gif","wb"); $tok=0; for($i=20;$i=3) break; } else { $tok=0; } } $i++;$i++; printf(" %d %d %d \n",$i,strlen($x),strlen($x)-$i); fwrite($fp,substr($x,$i,strlen($x)-$i)); fclose($fp); $fp=fopen("struktur.xxx","wb"); fwrite($fp,$x,strlen($x)); fclose($fp); ?> _________________________________________________________________ 22.5. Wie kann ich eine Volltextsuche realisieren? | [2409]Suche | [2410]Text | [2411]Index | [2412]Suchmaschine | [2413]Wildcard | [2414]Joker | [2415]Datenbank | Antwort von [2416]Kristian Köhntopp Eine Suchmaschine fuer Volltext wird man in den meisten Faellen nicht in PHP und mit einer SQL-Datenbank programmieren, sondern sinnvollerweise für diesen Anwendungszweck spezialisierte Software verwenden. SQL-Datenbanken sind nur dann optimal eingesetzt, wenn die Art der Daten und die Art der Anfragen den Einsatz von Indices möglich machen. Die meisten SQL-Datenbanken sind von Haus aus nicht besonders gut eingerichtet, um Indices über Volltext verwalten zu können: Zum einen können viele SQL-Datenbanken BLOB und TEXT-Felder gar nicht indizieren. Zum anderen können die meisten Datenbanken vorhandene Indices nicht nutzen, wenn der Suchausdruck nicht ohne Wildcard am vorderen Rand der Spalte verankert ist, d.h. wenn die Suche die Form LIKE '%suchwort' hat. MySQL bietet ab der Version 3.23.23 die Möglichkeit, einen Volltextindex anzulegen. Eine Anleitung dazu befindet sich in [2417]Wie realisiere ich eine Volltextsuche mit MySQL? Einige populäre Volltextsuchmaschinen: * [2418]ht://dig * Glimpse * [2419]mnoGoSearch (ehemals UDMSearch) * [2420]Isearch In der Newsgroup wurde der folgende Text zum Studium empfohlen: [2421]Managing Gigabytes; Compressing and Indexing Documents and Images , Ian H. Witten, Alistair Moffat, Timothy C. Bell; [2422]Morgan Kaufmann Publishers . 22.6. Wie kann ich mit PHP News lesen und schreiben? | [2423]Newsgroup | [2424]IMAP | [2425]NNTP | [2426]News | [2427]Webnews | Antwort von [2428]Kristian Köhntopp Mit Hilfe der IMAP-Bibliothek und IMAP-Funktionen kann man auch auf Newsserver zugreifen. Auf den Webseiten von [2429]Floh findet man einen in PHP geschriebenen [2430]Newsclient . Dieser verwendet jedoch nicht die IMAP-Funktionen, sondern bildet diese Funktionen manuell nach, da das IMAP-Modul bei vielen PHP-Installationen von Webhostern nicht verfügbar ist. 22.7. Wie kann ich einen Onlineshop mit PHP realisieren? | [2431]Shop | Antwort von [2432]Kristian Köhntopp Fertige Onlineshoplösungen in PHP: * [2433]Caupo Shop * [2434]FishCart SQL * [2435]phpShop * [2436]The Exchange Project 22.8. Wie kann ich die IP des Users erfahren? | [2437]IP | [2438]Adresse | [2439]Benutzer | [2440]Client | [2441]Browser | [2442]Proxy | [2443]Host | Antwort von [2444]Johannes Frömter In der Umgebungs-Variablen REMOTE_ADDR steht die IP-Adresse des Rechners , der die Anfrage sendet. Dies ist nicht zwangsläufig der Rechner, an dem der User sitzt - es kann genausogut ein Proxy sein. Wenn der Benutzer in einer Firma mit mehr als 2-3 PCs sitzt, ist letzteres sogar sehr wahrscheinlich, aber es kann auch bei ganz normalen Provider-Endkunden so sein. Folgendes Skript verwendet die Umgebungs-Variable REMOTE_ADDR und versucht, den Hostnamen zur IP mit der Funktion [2445]gethostbyaddr() zu ermitteln (sog. reverse lookup). _________________________________________________________________ _________________________________________________________________ Kommt die Verbindung über einen Proxy zustande, kann es sein, daß dieser die IP "seines" Clients im HTTP-Header weitergibt. Der verbreitete Proxy squid beispielsweise nennt diesen Header X-Forwarded-For , dessen Inhalt dann (wie andere HTTP-Header auch) in einer Umgebungsvariablen zur Verfügung gestellt wird ( HTTP_X_FORWARDED_FOR ). Warum sich die IP-Adresse prinzipiell nicht zur Identifizierung von Benutzern eignet, wird in folgendem Artikel näher erläutert: " [2446]Warum verwendet PHPLIB nicht die IP-Nummer des Browsers als Schutz gegen eine Übernahme der Session? " 22.9. Wie kann ich ein JPEG-Bild verkleinern? | [2447]Bild | Antwort von [2448]Kristian Köhntopp Auf [2449]http://www.phpwelt.de/tutorials/tutorials.php?tid=16 wird dies beschrieben. 22.10. Wie kann ich die Performance zweier Befehle vergleichen? | [2450]Performance | [2451]Benchmark | [2452]Geschwindigkeit | [2453]Vergleich | Antwort von [2454]Martin Jansen _________________________________________________________________ \n", $event); list($low, $high) = split(" ", microtime()); $t = $high + $low; flush(); return $t; } function next_timer($start, $event) { list($low, $high) = split(" ", microtime()); $t = $high + $low; $used = $t - $start; printf("timer: %s (%8.4f)
\n", $event, $used); flush(); return $t; } $t = start_timer("start Befehl 1"); /* Hier den ersten Befehl einfuegen */ $t = next_timer($t, "start Befehl 2"); /* Hier den zweiten Befehl einfuegen */ $t = next_timer($t, "finish"); ?> _________________________________________________________________ Möchte man zum Beispiel den Performance-Unterschied zwischen mysql_fetch_row und mysql_fetch_array bestimmen, fügt man die beiden Befehle an die beiden mit Kommentaren versehenen Stellen im Skript ein. Antwort von [2455]Sebastian Bergmann Eine Alternative zur oben dargestellten Methode der Performance Messung bieten die PEAR Klassen Benchmark_Timer und Benchmark_Iterate. Die Benchmark_Timer Klasse stellt die benötigte Funktionalität zur Verfügung, um Marken zu setzen, und die zwischen zwei Marken verstrichene Zeit zu berechnen. _________________________________________________________________ setMarker("Beginn For-Schleife"); for ($i = 0; $i < 10000; $i++) { } // Marke "Ende For-Schleife" setzen $Timer->setMarker("Ende For-Schleife"); // Zeit zwischen den beiden Marken berechnen print $Timer->timeElapsed("Beginn For-Schleife","Ende For-Schleife"); ?> _________________________________________________________________ Die Benchmark_Iterate Klasse leitet sich von der Benchmark_Timer Klasse ab und ermöglicht die wiederholte Ausführung einer Funktion, um so die mittlere Ausführungsgeschwindigkeit derselben zu ermitteln. _________________________________________________________________ "; } // PEAR::Benchmark_Iterate inkludieren require_once "PEAR/Benchmark/Iterate.php"; // Benchmark Instanz erzeugen $Benchmark = new Benchmark_Iterate; // Benchmark ausführen $Benchmark->run(100, "foo"); // Ergebnis ausgeben $result = $Benchmark->get(); print "Mittlere Ausführungsgeschwindigkeit: " . $result["mean"]; ?> _________________________________________________________________ 22.11. Wie kann ich den Inhalt eines Verzeichnisses samt dem Inhalt aller Unterverzeichnisse ausgeben? | [2456]Verzeichnis | [2457]Unterverzeichnis | [2458]auflisten | [2459]rekursiv | Antwort von Guido Haeger Um nicht nur den Inhalt des aktuellen Verzeichnisses, sondern auch den Inhalt aller Unterverzeichnisse ausgeben zu können, muss man eine rekursive Funktion verwenden. Diese ruft sich bei Bedarf selbst auf. Im nachfolgenden Beispiel durchläuft die Funktion show_dir jeweils das aktuelle Verzeichnis. Wird Datei gefunden, wird der Dateiname ausgegeben. Findet die Funktion ein Verzeichnis, dann wird der Verzeichnisname fett ausgegeben und die Funktion ruft sich mit dem Unterverzeichnis als Parameter selbst wieder auf. _________________________________________________________________

";
     }

     $handle = @opendir($dir);
     while ($file = @readdir ($handle))
     {
        if (eregi("^\.{1,2}$",$file))
        {
          continue;
        }

     if(is_dir($dir.$file))
        {
          printf ("% ".$pos."s %s\n", "|-", $file);
          show_dir($dir.$file."/", $pos + 3);
        }
        else
        {
          printf ("% ".$pos."s %s\n", "|-", $file);
        }
     }
     @closedir($handle);

     if($pos == 2)
     {
          echo "

"; } } show_dir("special/"); ?> _________________________________________________________________ 22.12. Wie kann ich aus einem Zahlenbereich von x bis y, zufällig n Zahlen auswählen, so daß keine Zahl doppelt vorkommt? | [2460]Zufall | [2461]Zahlen | [2462]Duplikat | [2463]einmalig | Antwort von Guido Haeger Häufig werden dazu Schleifen programmiert, die pro Durchlauf eine Zufallszahl aus dem vorgegebenen Bereich ermitteln. Danach wird geprüft, ob diese Zufallszahl schon ermittelt wurde. Ist das der Fall, wird die Schleife erneut durchlaufen. Da insbesondere bei PHP3 die Abarbeitung von Schleifen nicht besonders effizient ist, kann es bei dieser Methode sehr lange dauern, bis die gewünschte Anzahl eindeutiger Zufallszahlen ermittelt wurde. _________________________________________________________________ _________________________________________________________________ 22.13. Wie kann ich zählen, wie oft auf einen Link geklickt wurde? | [2464]Link | [2465]Counter | Antwort von [2466]Martin Jansen Dem Skript liegt folgende Struktur der MySQL-Tabelle zugrunde: _________________________________________________________________ mysql> describe counter; +-------+------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------+------------------+------+-----+---------+----------------+ | id | int(10) unsigned | | PRI | 0 | auto_increment | | url | char(255) | | | | | | count | int(11) | | | 0 | | +-------+------------------+------+-----+---------+----------------+ 3 rows in set (0.00 sec) _________________________________________________________________ Das Feld url enthält die URL, die aufgerufen wird. Das Feld count enthält die Anzahl der Klicks auf url . _________________________________________________________________ _________________________________________________________________ Anwendungsbeispiel: Link Als Parameter für die Datei count.php wird die URL übergeben, auf die weitergeleitet werden soll. In count.php wird nun der Datensatz in der Tabelle, der $url als Wert für das Feld url enthält um 1 erhöht und es wird auf die neue URL weitergeleitet. 22.14. Wie kann ich das Datum der letzten Änderung einer Datei erfahren? | [2467]Datei | [2468]Datum | [2469]Aenderung | [2470]Zeit | Antwort von [2471]Daniel T. Gorski _________________________________________________________________ _________________________________________________________________ Möchte man unter UNIX-Derivaten das Datum der letzten Änderung der zugehörigen Inodes (z.B. Änderungen der Filesystem-Rechte) erfahren, so ist die Funktion [2472]filectime() zu benutzen. 22.15. Welche in PHP realisierte Foren gibt es? | [2473]Forum | [2474]Thread | [2475]Diskussion | [2476]Community | [2477]Board | Antwort von [2478]Martin Jansen Wenn man ein Forum in PHP selbst schreiben möchte, kann der Artikel [2479]"Threaded Discussion with PHP/MySQL" als Grundlage dienen. Man kann alternativ auf die folgenden in PHP geschriebenen Foren zurückgreifen: * [2480]APBoard - Another PHP Board * [2481]Netzbrett * [2482]Phorum * [2483]phpBB * [2484]Powie's PHP Forum 22.16. Wie biete ich meine Seiten mehrsprachig an? | [2485]Sprache | [2486]international | [2487]i18n | Antwort von [2488]Martin Jansen Auf der Seite [2489]http://www.php-center.de/artikel/i18n.php3 findet man einen Artikel, der sich mit der Internationalisierung von PHP-generierten Seiten befasst. 22.17. Wie kann ich ermitteln, wieviele Besucher gerade meine Seite betrachten? | [2490]User | [2491]Besucher | [2492]Homepage | [2493]online | [2494]HTTP | Antwort von Markus Dobel Es gibt keine zuverlässige Methode, dies herauszufinden, da HTTP ein zustandsloses Protokoll ist und daher keine Stati "eingeloggt, ausgeloggt" wie bei FTP, Telnet etc. vorhanden sind. Der Client (Browser) baut eine Verbindung zum Webserver auf, fordert eine Seite mit all ihren Elementen (Bildern, JavaScript etc.) an und beendet danach die Verbindung wieder. Es entsteht also immer nur eine punktuelle, temporär andauernde Verbindung zwischen dem Webserver und dem Client des Anwenders. Mit Hilfe von PHPlib und den sess_*-Funktionen von PHP4 kann man einen Besucher zwar wiedererkennbar machen, man kann damit jedoch nur feststellen, wann ein Besucher zuletzt eine Seite angefordert hat. Wie lange er diese liest und daher noch "auf der Seite online" ist, kann man nicht herausfinden. Er könnte 30 Minuten an dem Text einer Seite lesen oder aber auch das Browserfenster direkt nach Anforderung der Seite schließen; der Server weiss nur, wann er die Seite wem ausgeliefert hat. Es gibt einige Denkansätze, wie man diese fehlende Funktionalität nachbauen könnte, welche jedoch alle von der Zusammenarbeit mit dem Browser des Besuchers abhängig sind, um halbwegs zuverlässig zu arbeiten und/oder nur Näherungsweise an die reellen Gegebenheiten herankommen. Ein paar davon bauen auf Javascript auf, welches man von vornherein ausklammern sollte, da nicht jeder Browser Javascript beherrscht und auch angeschaltet hat. Diese Methode ist also unzuverlässig. Eine weitere Idee basiert darauf, einen kleinen "Blindframe" auf der Seite zu platzieren, welcher regelmäßig automatisch per META-Tag neugeladen wird. Auch wird darauf vertraut, dass der automatische Refresh wirklich bei jedem Besucher ausgeführt wird und man verpflichtet seine Besucher dazu, einen Browser zu benutzen, der Frames beherrscht. Darüberhinaus vergrault man sich auf Dauer einige Besucher, die die ständige Netzaktivität des Browsers irritiert oder nervt. Zuletzt könnte man noch auswerten, wieviele Benutzer innerhalb der letzten x Minuten eine Seite angefordert haben und daher Annahmen darüber treffen, ob diese Besucher noch da sind. Auch dies ist keine zuverlässige Aussage. Zusammenfassend lässt sich also sagen, dass die Aussage "x User online" (wie sie auf vielen Sites zu finden ist) reines Blendwerk sind. Es ist technisch nicht möglich, diese Aussagen zu treffen. 22.18. Welche Webmail-Oberflächen in PHP gibt es? | [2495]Webmail | [2496]EMail | [2497]IMAP | [2498]POP3 | [2499]GMX | Antwort von [2500]Johannes Frömter Es gibt zahlreiche fertige PHP-Lösungen für den Zugriff auf EMail-Accounts per Weboberfläche. Viele von ihnen benutzen die [2501]IMAP -Library, die nicht bei jedem Provider installiert ist. Für das Verarbeiten und Speichern der Mails wird oft eine Datenbank benötigt, und zum Versenden von Mails wird teilweise das lokal installierte Mailprogramm benutzt - diese Voraussetzungen sollten bei der Auswahl also berücksichtigt werden. * [2502]AeroMail - IMAP-Webmail-Client für PHP3 oder PHP4. PHP muß als Apache-Modul laufen. * [2503]Basilix - IMAP- und MySQL-basierende Applikation in PHP3. * [2504]IMP - IMP ist der Webmail-Teil des Horde-Projektes. Die IMAP-Bibliothek von PHP sowie MySQL oder PostgreSQL sind nötig. Versionen für PHP3 (mit PHPLIB) und PHP4 vorhanden. * [2505]NOCC - In PHP4 geschriebener Webmail-Client für IMAP- und POP3-Accounts, keine Datenbank nötig, unterstützt zahlreiche Sprachen. * [2506]phpop - Webmailer basierend auf PHP3, MySQL und PHPLIB. * [2507]PHPost - Reiner POP3-Client für PHP4, kommt ohne Datenbank, IMAP-Modul und JavaScript aus. Unterstützt mehrere Sprachen. * [2508]popper - POP3-Webmail-Client für PHP4 und MySQL. * [2509]Postaci - Türkischer Webmailer (mehrsprachenfähig) für PHP4, SQL-Datenbank nötig, POP3- und IMAP-Unterstützung. * [2510]rymo - Kleiner POP3-Webmail-Client für PHP3/4. * [2511]SquirrelMail - Leistungsfähiger Webmail-Client für PHP4. Nur geeignet für IMAP-Mailboxen, benötigt dennoch nicht die IMAP-Bibliothek von PHP4! Plugin-fähig, zahlreiche Erweiterungen vorhanden. * [2512]TWIG - Groupware-Tool mit EMail-Funktion (IMAP), läuft auf PHP3 und PHP4, braucht eine Datenbank (MySQL oder PostgreSQL). 22.19. Wie überprüfe ich Hyperlinks auf ihre Gültigkeit? | [2513]Link | [2514]HTTP | [2515]404 | [2516]Test | [2517]Pruefung | Antwort von [2518]Johannes Frömter Indem man mit [2519]fsockopen() eine Verbindung zum Webserver herstellt, einen HTTP-HEAD-Request auf die zu überprüfende Ressource absetzt und die Antwort auswertet. Wer dies nicht selber programmieren will, kann Scripts wie z.B. die Funktion [2520]phpLinkCheck benutzen. 22.20. Wie erzeuge ich Excel-Dateien mit PHP? | [2521]Excel | [2522]Download | [2523]CSV | [2524]Import | Antwort von [2525]Johannes Frömter "Richtige" Excel-Dateien lassen sich kaum erzeugen, da .xls ein binäres, undokumentiertes proprietäres Microsoft-Format ist, das zudem von Version zu Version verändert wird. Stattdessen kann man aber Textdateien erzeugen, deren Werte (Spalten) mit Tabulatoren und deren Zeilen mit CRLF ( \r\n ) getrennt sind, und diese Excel als .xls-Datei "unterschieben". Wichtig ist hierbei, als Dateityp tatsächlich eine Excel-(.xls)-Datei zu deklarieren - obwohl Excel diverse Importformate wie .csv (comma separated value) kennt, funktioniert es damit, vermutlich mangels definiertem Content-Type, nicht wie gewünscht. _________________________________________________________________ header("Content-Type: application/vnd.ms-excel"); header("Content-Disposition: inline; filename=\"excel.xls\""); readfile($filename); // Datei 1:1 durchreichen // echo "Titel (A1)\r\nA2\tB2\r\nA3\tB3"; // ODER mit PHP erzeugen _________________________________________________________________ Anmerkung: Trotz der Angabe inline bietet der Browser i.d.R. einen Dialog an, der das direkte Öffnen oder aber das Speichern der Datei ermöglicht. Die Angabe attachment dagegen führte zu kuriosen, nicht brauchbaren Ergebnissen... Ein gänzlich anderer Weg, Excel-Dateien zu erzeugen, führt über die [2526]COM-Funktionen von PHP4. Damit kann man eine Excel-Instanz direkt ansteuern und so u.a. eine echte .xls-Datei generieren. Wer das ausprobieren will, der lese diesen [2527]Artikel und besorge sich die [2528]Excel Class von Alain Samoun. 22.21. Wie kann ich prüfen, ob eine IP-Adresse in einem bestimmten Bereich liegt? | [2529]IP | [2530]Range | [2531]Adresse | Antwort von [2532]Johannes Frömter PHP4 bietet die Funktion [2533]ip2long() , mit der man eine IP-Adresse in Punktschreibweise ("dotted quad") in die numerische Form umrechnen kann, was einen einfachen größer/kleiner-Vergleich erlaubt. Der Haken daran: bei PHP beträgt der Maximalwert eines Integer-Typen etwas über 2 Milliarden (genau: 2.147.483.647, vorzeichenbehafteter 32-Bit-Wert), was für die Umrechnung von IP-Adressen nicht ausreicht; bei 127.255.255.255 ist Schluß, danach wechselt das Vorzeichen. Wandelt man die IP dagegen in einen String um, gibt es dieses Limit nicht. Dann kann man einfach mittels [2534]if() oder [2535]strcmp() vergleichen: _________________________________________________________________ if (ip2str($start) <= ip2str($ip) AND ip2str($ip) <= ip2str($ende)) ... function ip2str($ip) { $ip = preg_replace("/(\d{1,3})\.?/e", 'sprintf("%03d", \1)', $ip); return (string)$ip; } _________________________________________________________________ 22.22. Wie kann ich prüfen, ob eine bestimmte ICQ UIN online ist? | [2536]ICQ | Antwort von [2537]Kristian Köhntopp Es gibt einige fertige Scripte, die mit Hilfe von PHP testen können, ob eine bestimmte UIN online ist. Alle diese Scripte basieren auf der Auswertung der Größe einer [2538]Grafik , die ICQ auf den [2539]ICQ Whitepages bereitstellt. * [2540]ICQ Status Checker von Bill Zeller * [2541]ICQ class * [2542]ICQ EmailExpress Wenn die Scripte unzuverlässige Ergebnisse liefern, dann liegt das nicht an den Programmen, sondern an den ICQ-Servern, die mitunter recht zufällige Stati melden... 22.23. Welche kostenlose Portalsoftware in PHP gibt es? | [2543]Portal | [2544]Homepage | [2545]Community | [2546]News | Antwort von [2547]Martin Jansen Es gibt einige fertige Systeme, mit denen sich relativ schnell eine kleine Portalseite mit diversen Funktionen aufziehen lässt: * [2548]Dark Hart Portal * [2549]eZ publish * [2550]PHP-Nuke * [2551]phpWebSite 22.24. Welche Lösungen gibt es, um Diagramme zu erstellen? | [2552]Graphen | [2553]Diagramm | [2554]Balken | [2555]Torten | [2556]Statistik | [2557]Grafik | Antwort von [2558]Sebastian Bergmann Es gibt einige fertige Systeme, die einem die Funktionen der diversen Grafikbibliotheken von PHP so abstrahieren, dass auch die Erstellung komplexer Diagramme recht einfach von der Hand geht: * [2559]advGraph * [2560]Graphite Graphing Class * [2561]JpGraph * [2562]php3DLib * [2563]phpBarGraph * [2564]PHPLOT * [2565]VAGRANT * [2566]VH Graph * [2567]ykcee 22.25. Wie überprüfe ich, ob eine Zahl gerade oder ungerade ist? | [2568]gerade | [2569]ungerade | [2570]pruefen | Antwort von [2571]Martin Jansen Der einfachste Weg dies zu testen, ist die Zahl durch 2 zu teilen und zu überprüfen, ob die Division einen Rest zurückgibt. In PHP kann man dies wie folgt realisieren: _________________________________________________________________ _________________________________________________________________ 22.26. Wie kann ich ID3-Tags von MP3-Dateien lesen/schreiben? | [2572]Script | [2573]MP3 | [2574]ID3 | [2575]Tag | [2576]Datei | [2577]lesen | [2578]schreiben | Antwort von [2579]Johannes Frömter Es gibt etliche fertige Scripte, die ID3-Tags lesen und schreiben können: * [2580]class.id3.php * [2581]getID3() * [2582]MP3 class * [2583]Weitere Scripte (Hotscripts.com) Die ID3-Tags Version 1 (ID3v1) sind relativ simpel, da die Länge der einzelnen Felder genau definiert ist und der Datenblock einfach die letzten 128 Byte der Datei belegt. ID3v2-Tags dagegen sind wesentlich komplexer, da sich diese am Anfang der Datei befinden und zudem eine variable Länge aufweisen (sie können bis zu 256 MB groß sein, Texte in Unicode, Links, ja sogar Bilder enthalten); die genaue Spezifikation gibt es unter [2584]http://www.id3.org/ . 22.27. Wie kann ich eine whois-Abfrage mit PHP realisieren? | [2585]Script | [2586]whois | [2587]Domain | [2588]Abfrage | [2589]NIC | [2590]TLD | Antwort von [2591]Johannes Frömter Um den Status einer Domain in Erfahrung zu bringen, stellt das jeweilige NIC (Network Information Center) einen sog. whois-Dienst für die von ihm verwaltete TLD (Top Level Domain, .com , .de , .info etc.) zur Verfügung. Dabei gibt es in der Praxis zwei Probleme: erstens sind die Adressen der whois-Server nicht wirklich einheitlich, und zweitens variiert das Ausgabe-Format von Anbieter zu Anbieter sehr stark - der eine liefert z.B. die komplette Anschrift des Domaininhabers, der andere sagt nur "besetzt" oder "frei". Die meisten whois-Scripte werten deshalb die Antwort des Server nicht aus, sondern liefern sie 1:1 als Text zurück. Hier eine Liste von in PHP programmierten whois-Scripten: * [2592]MWhois bringt eine ausführliche Liste von NICs mit und parst die Antworten der Server nach Strings wie "No entries found", um den Status zu ermitteln. Kann durch Templates an verschiedene Sprachen angepaßt werden. * [2593]whoislookup arbeitet überwiegend nach dem Schema whois.nic.tld . Die Server-Antwort wird ungeparst präsentiert. * [2594]whois2 arbeitet mit einer eigenen NIC-Liste. Für ausgewählte NICs gibt es Erweiterungen, die das Parsen der Server-Antworten erledigen. * [2595]whoisclass überläßt die ganze Arbeit dem Server whois.geektools.com , der seinerseits die tatsächlichen whois-Server abfragen muß. Für stark frequentierte Seiten ist es ratsam, den [2596]GeekTools Proxy auf dem eigenen Server zu installieren, um den Traffic in Grenzen zu halten. 23. Guter Code 23.1. [2597]Vermeide globale Variablen 23.2. [2598]Halte Code links. Verwende Wächter statt Schachtel-if 23.3. [2599]'or' und 'and' sparen Klammern 23.4. [2600]Prüfe importierte Parameter. Traue niemandem 23.5. [2601]Von HTML zu PHP: Schreibe Formularverarbeitungen in Normalform 23.6. [2602]Trenne Aussehen und Inhalt 23.7. [2603]Was sind eigentlich if-Schleifen? 23.8. [2604]Anführungzeichen oder Hochkomma? 23.1. Vermeide globale Variablen | [2605]Namensraum | [2606]GET | [2607]POST | [2608]Cookie | [2609]Parameter | Antwort von [2610]Kristian Köhntopp In PHP Version 3 werden eine ganze Menge Daten aus der Prozessumgebung und aus dem Internet in den globalen Namensraum importiert. Das Verhalten von PHP ist dabei leicht unterschiedlich, je nachdem, ob das Modul oder die CGI-Version verwendet werden. Es gelten die folgenden Regeln: * Umgebungsvariablen, die als globale Variablen importiert wurden, werden durch GET, POST oder COOKIE-Parameter überschrieben. Das ist schlecht, weil auf diese Weise Parameter aus halb vertrauenswürdiger Quelle (dem Prozeß-Environment) durch Daten aus vollkommen vertrauensunwürdiger Quelle (dem Internet) überschrieben werden. Abhilfe: Ausschließlich [2611]getenv() zum Zugriff auf Umgebungsvariablen verwenden. _____________________________________________________________ \n"; echo getenv("PWD")."
\n"; print "
\n"; phpinfo(); ?> _____________________________________________________________ * Track-Variablen wie $HTTP_GET_VARS werden durch GET, POST oder COOKIE-Parameter überschrieben. Das führt zu lustigen Effekten wie dem Absturz des Scriptes _____________________________________________________________ _____________________________________________________________ mitten in der Ausgabe, weil [2612]phpinfo() sich fälschlicherweise darauf verläßt, daß HTTP_GET_VARS ein Array ist (Fehler ist in Version 3.0.15 korrigiert). Abhilfe ist nicht möglich, man kann lediglich versuchen, Zugriffe auf diese Variablen durch einen [2613]is_array() -Test abzusichern. * Variablen aus GET, POST und COOKIE-Parametern werden in der Reihenfolge GPC oder der durch den Konfigurationsparameter [2614]gpc_order vorgegebenen Reihenfolge importiert. * PHP-Einbauvariablen wie $PHP_SELF werden zuletzt definiert und können nicht überschrieben werden. Ebenso Variablen aus dem Apache-Environment, wie etwa $SERVER_NAME . Aber Vorsicht: In CGI-PHP ist $SERVER_NAME eine Umgebungsvariable und kann durch GET-Parameter überschrieben werden. Sicherer Zugriff ist nur mit getenv("SERVER_NAME") möglich. In der Modulversion ist $SERVER_NAME eine Variable aus dem Apache-Environment, wird nicht überschrieben und ist nicht durch [2615]getenv() erreichbar. Ein anderer Fehler, der leicht ausbeutbar ist, ist die Verwendung nicht initialisierter globaler Variablen. Durch Einsetzen von Werten als GPC-Parameter kann ein Angreifer diese Variablen mit beliebigen Startwerten vorbelegen, so daß die Annahme nicht zutreffend ist, daß uninitialisierte Werte immer 0 , "" oder false sind. Durch Hochsetzen des Warning Levels (siehe [2616]error_reporting und [2617]error_reporting() ) kann man solche Probleme zuverlässig aufspüren. In PHP Version 4 ist der globale Namensraum wesentlich besser kontrollierbar. Man kann das automatische Importieren von externen Variablen in den globalen Namensraum gut verhindern. Werte finden sich stattdessen in getrennten, gegenseitig nicht überschreibbaren Namensräumen (vordefinierten Hashes mit unterschiedlichen, nichtüberlappenden Namen). Der Default ist jedoch PHP Version 3-Kompatibilität - man muß die Default-Importe zunächst einmal abschalten (siehe [2618]register_globals ). Der Namensraum von Funktionen und Klassen ist wesentlich besser kontrollierbar. Es ist daher empfehlenswert, so viel Funktionalität als möglich in Funktionen oder Klassen abzulegen und dort kontrolliert Variablen als Funktionsparameter oder über [2619]global() zu importieren. 23.2. Halte Code links. Verwende Wächter statt Schachtel-if | [2620]Schleife | [2621]Funktion | [2622]If | Antwort von [2623]Kristian Köhntopp In der strukturierten Programmierung erzeugt man Schleifen ohne Schleifenkurzschlüsse mit continue oder break und Funktionen mit genau einem Funktionsausgang durch return . Dadurch entsteht häufig Code mit sehr vielen geschachtelten Abfragen. Meistens versucht man vor der eigentlichen Nutzlast einer Schleife oder einer Funktion eine Reihe von Vorbedingungen zu testen, die für den erfolgreichen Einsatz der Nutzlast sichergestellt sein müssen. Der generierte Code sieht dann wie folgt aus: _________________________________________________________________ if (vorbedingung) { if (vorbedingung2) { if (vorbedingung3) { doit(); // Nutzlast } else { handle_error3(); } else { handle_error2(); } } else { handle_error(); } _________________________________________________________________ Dies ist sehr schwer zu lesen und zu verstehen, weil der eigentliche Zweck der Funktion tief geschachtelt und sehr unübersichtlich versteckt ist. Geübte Programmierer verwenden stattdessen absichtlich die in der strukturierten Programmierung verpönten Schleifenkurzschlüsse und frühzeitigen Funktionsausstiege in einer bestimmten Form, um besser lesbaren Code zu schreiben: _________________________________________________________________ if (!vorbedingung) handle_error(); if (!vorbedingung2) handle_error2(); if (!vorbedingung3) handle_error3(); doit; // Nutzlast _________________________________________________________________ Dieser Code skaliert sich besser: Egal wieviele Vorbedingungen zu erfüllen sind - die Einrücktiefe bleibt konstant. Außerdem steht der normale Fall jetzt in Falllinie und der Fehlercode ist als Ausnahme eingerückt und zur Seite gedrängt. Ein praktisches Beispiel: Der folgende Code zum Durchlesen eines Verzeichnisses _________________________________________________________________ $d = dir("d:/logfiles"); while($entry=$d->read()) { if (($entry != ".") && ($entry != "..")) { doit(); } } $d->close(); _________________________________________________________________ wird durch die Umstellung zu _________________________________________________________________ $d = dir("d:/logfiles"); while ($entry = $d->read()) { if ($entry == "." or $entry == "..") continue; doit(); } $d->close(); _________________________________________________________________ 23.3. 'or' und 'and' sparen Klammern | [2624]If | [2625]Operator | Antwort von [2626]Kristian Köhntopp 23.4. Prüfe importierte Parameter. Traue niemandem | [2627]GET | [2628]POST | [2629]Cookie | [2630]Parameter | [2631]Formular | [2632]JavaScript | [2633]Referer | [2634]Plausibilitaet | [2635]Trust Boundary | [2636]Vertrauen | Antwort von [2637]Kristian Köhntopp Parameter, die als GET, POST oder COOKIE-Werte in das lokale Programm gekommen sind, sind nicht vertrauenswürdig. Techniken, die Variablen als GET-Parameter am Ende einer URL durchschleifen oder als HIDDEN-Variablen in Formularen weitergeben ( Ping-Pong-Technik ), sind daher prinzipbedingt fehlerhaft und können niemals sicher sein. Auch dürfen Variablen aus GPC-Quellen niemals ungeprüft direkt verwendet werden, sondern müssen zwingend erst einmal auf Ungefährlichkeit geprüft werden. Bei Parametern, die gegen die Empfehlung per Ping-Pong weitergereicht werden, muß dieser Test jedesmal (auf jeder Seite) wieder gemacht werden, da ja ein Anwender oder ein böswilliges Programm die Werte inzwischen geändert haben könnte. Eine Webanwendung kontrolliert nur den Bereich bis zur Firewall. Der Browser des Anwenders befindet sich auf der anderen Seite der Firewall und ist daher als Feindesland anzusehen. Daten, die von dort kommen, können unerwartete Werte oder unerwartete Formate haben. Insbesondere kann eine Webanwendung keine Annahmen über bestimmte Eigenschaften des Browsers machen, wie zum Beispiel "JavaScript wird zuverlässig ausgeführt", "Cookies werden akzeptiert und verändern ihre Werte nicht spontan" oder "Referer-Header sind vertrauenswürdig". Es gelten daher die folgenden Empfehlungen: * Jede Form von Ping-Pong, also Wert-Weitergabe durch GET-Parameter, HIDDEN-Variablen und dergleichen ist zu vermeiden. Auch durch Codierung der Werte ist hier nichts zu erreichen. Stattdessen sind Sessionvariablen zu verwenden. Nur die Session-ID wird von einer Seite an eine andere Seite weitergereicht. * Keinesfalls darf ein Programm Werte aus einer GET, POST oder COOKIE-Quelle direkt verwenden. Jeder externe Wert ist einer Plausibilitätsprüfung zu unterziehen, bevor er verwendet wird. * Validierung von Eingabewerten muß serverseitig durch PHP geschehen. JavaScript-Validatoren sind nicht vertrauenswürdig: Der Browser des Anwenders führt diese Validatoren möglicherweise nicht aus, auch dann, wenn er sich durch die User-Agent-Zeile als JavaScript-fähiger Browser identifiziert. Ebenso muß angenommen werden, daß die Referer-Header des Browsers möglicherweise gefälscht sind. Man kann nicht annehmen, daß ein Zugriff tatsächlich von einer bestimmten vorhergehenden Seite hierher vermittelt wurde. Zusammenfassend: Traue niemandem. Validiere allen Input oder stirb. 23.5. Von HTML zu PHP: Schreibe Formularverarbeitungen in Normalform | [2638]Redirect | [2639]Affenformular | [2640]Formular | Antwort von [2641]Kristian Köhntopp Da jede HTML-Datei auch ein gültiges, bedeutungsgleiches PHP-Programm ist, existiert ein einfacher und systematischer Weg, um von einem HTML-Formular zu einem PHP-Programm zu kommen, das dieses Formular bearbeitet. Hält man sich an diesen Weg, wird das resultierende Formular zugleich ein bestimmtes Format haben. Der erste Schritt ist die Entwicklung eines reinen HTML-Formulares, das die zu verarbeitenden Daten abfragt. In einem zweiten Schritt wird man aus diesem Formular ein sogenanntes Affenformular machen, indem man dafür sorgt, daß das Formular sich selbst aufruft und seine alten Werte immer wieder einsetzt. Es heißt Affenformular, weil eine Million Affen dieses Formular eine Million mal aufrufen können, ohne etwas zu bewirken. _________________________________________________________________

_________________________________________________________________ Das Affenformular enthält an zwei Stellen PHP-Code: Die Action des Formulares ist das Formular selbst und durch das Einsetzen von $PHP_SELF kann das Formular seine eigene Adresse bestimmen und sich so selbst aufrufen. Und außerdem werden die Defaultwerte der verschiedenen Formularfelder durch PHP wieder mit den Ausgangswerten belegt. Wenn das Formular also abgesendet wird und die Eingabewerte nicht korrekt sind, wird das Formular wieder dargestellt und die alten Werte werden als Standardwerte wieder eingesetzt, damit der Anwender sie korrigieren kann, ohne sie noch einmal eingeben zu müssen. Der dritte Schritt besteht dann darin, eine Reihe von Funktionen zu codieren, die die Werte aus diesem Formular validieren und ggf. die passenden Fehlermeldungen erzeugen, die dann an den geeigneten Stellen wieder in das Formular eingesetzt werden. Das Endresultat muß dann eine Variable haben, an der entscheidbar ist, ob der Formularinhalt gültig ist oder nicht. _________________________________________________________________


_________________________________________________________________ Schließlich kann man im vierten Schritt daran gehen, die validierten Formulardaten einer Bearbeitungsfunktion zu übergeben und diese Funktion die eigentliche Arbeit machen zu lassen. Das Ergebnis dieser Verarbeitung wird in den meisten Fällen unterhalb des Formulars dargestellt werden, so daß man mit den Eingabedaten oben gleich die nächste Abfrage starten kann. An dieser Stelle hat man ein funktionierendes Formular und zwei Baustellen, an denen man weiterarbeiten kann: Zum einen muß man sich Gedanken darüber machen, welche Funktionalität aus diesem Formular an anderer Stelle so auch wieder verwendet werden könnte und sollte sich überlegen, ob man nicht eine Klasse schreiben möchte, in die man diese Funktionalität anwendungsunabhängig kapseln könnte. Auf diese Weise wird man sich Schritt für Schritt eine kleine Bibliothek an Funktionen zulegen, die in vielen anderen Projekten ebenfalls Anwendung finden kann. Zum anderen muß man sich überlegen, ob man die Verkettung von Bildschirmen, Knöpfen und Aktionen nicht ein wenig globaler lösen kann und welche Struktur man seinem Programm dafür geben wird. 23.6. Trenne Aussehen und Inhalt | [2642]Template | [2643]phplib | [2644]fasttemplate | [2645]Integrated Template | [2646]IT | [2647]ITX | [2648]Smarty | Antwort von [2649]Martin Jansen Die Vorteile der Trennung von Aussehen und Inhalt durch Verwendung von Templates werden im Kapitel [2650]Was sind Templates? Warum sind Templates nützlich? erläutert. Die gängigsten Template-Systeme in PHP sind: * [2651]Die Template-Klasse der PHPLIB * [2652]FastTemplate * [2653]Integrated Template [Extension] - IT[X] * [2654]Smarty 23.7. Was sind eigentlich if-Schleifen? | [2655]if | [2656]Bedingung | Antwort von [2657]Johannes Frömter Sogenannte "if-Schleifen" sind anscheinend nicht auszurottende Hirngespinste. Es gibt schlicht und einfach keine "if-Schleifen", weder in PHP noch in irgendeiner anderen Programmiersprache. Es gibt [2658]if -Anweisungen, die einen Codeteil abhängig von einer Bedingung ausführen oder nicht, und es gibt [2659]for -, [2660]while - und [2661]do-while -Schleifen, die einen Codeteil mehrmals ausführen. Schleifen und Bedingungen können selbstverständlich verschachtelt werden, d.h. in einer Schleife kann eine Bedingung abgefragt werden, oder abhängig von einer Bedingung kann eine Schleifenkonstruktion ausgeführt werden. Aber "i