Mittwoch, Juli 02, 2008
Hostpoint-Problem des Monats: Zeichensalat
Kein Monat vergeht, in dem Hostpoint nicht eine Überraschung parat hat. Während ich im Juni das erste Mal seit langem etwas Positives berichten durfte, war klar, dass im Juli garantiert wieder etwas kaputt gehen musste.
Und tatsächlich: Heute erhalte ich ein Mail eines Kunden, der über komische dargestellte Sonderzeichen flucht. Nach dem ich die Homepage angesurft habe, kann ich das Problem bestätigen: Irgendwie scheinen da UTF-8 und ISO-8859-1 durcheinander gekommen zu sein. Seit sechs Jahren hat die Web-Site keine Probleme mit Zeichensätzen aufgewiesen, doch nun ist über Nacht wohl etwas "kaputt" gegangen.
Soweit ich erkennen konnte, liegt das Problem darin begründet, dass mysql_query() neu nicht mehr ISO-8859-1-kodierte Zeichensätze zurückliefert, sondern UTF-8. Das HTML-Dokument sagt von sich aber, dass es in ISO-8859-1 kodiert ist - und htmlentities() erwartet auch ISO-8859-1. Ah, und die Tabellen-Spalten weisen ebenfalls latin1_german1_ci als Kodierung auf (jedenfalls sagt mir das phpMyAdmin so).
Temporärer Workaround
mysql_query("SET NAMES latin1");
... zuoberst in der index.php (natürlich nach dem Initialisieren der Datenbankverbindung!)
Jetzt klappt es wieder mit den Zeichensätzen.
Mal schauen, was sich Hostpoint für den kommenden Monat einfallen lässt.
Labels: Hosting, MySQL, PHP, Schweiz, Web-Entwicklung
Dienstag, Juni 24, 2008
Xdebug-Meldungen verfeinern
Wie man Xdebug installiert, habe ich hier bereits beschrieben. Doch mit der Installation alleine ist es noch nicht getan; das Teufelswerkzeug muss nun auch noch derart konfiguriert werden, dass es dem Entwickler alle wichtigen Informationen liefert.
Die Jungs drüben bei IBM haben sich die Mühe gegeben, die (bezüglich INI-Einstellungen schwer durchschaubare) Dokumentation zu lesen und ihre Konfigurationsparameter im Netz zu publizieren:
xdebug.dump_once = On xdebug.dump_globals = On xdebug.dump_undefined = On xdebug.dump.SERVER = REQUEST_METHOD,REQUEST_URI,HTTP_USER_AGENT xdebug.dump.REQUEST=* xdebug.show_exception_trace = On xdebug.show_local_vars = 1 xdebug.var_display_max_depth = 6
Quelle: Squash bugs in PHP applications with Xdebug
Tönt gut und produziert äusserst detaillierte Fehlermeldungen.
Wer nichts vom korrekten Initialisieren von Variablen hält, sollte xdebug.dump_undefined vielleicht lieber auf Off schalten ...
Wer auf Exceptions setzt und diese sauber abfängt, sollte vielleicht auch xdebug.show_exception_trace auf Off schalten ...
Labels: Linux, PHP, Web, Web-Entwicklung
Dienstag, Juni 10, 2008
Apache 1.3, MySQL 5 und PHP 5 unter Mac OS X auf UTF-8 trimmen
Mittlerweile habe auch ich den AMP-Stack auf meinem MacBook installiert und entwickle damit Web-Applikationen. Damit es bezüglich den Zeichensätzen koscher zu und her geht, musste ich folgende zwei Anpassungen an der Konfiguration vornehmen:
Apache 1.3
(Ich verwende aus Faulheit den mit Tiger mitgelieferten Apache - leider halt noch nicht 2.x)
In der /etc/httpd/httpd.conf wird mit folgendem Befehl eingestellt, dass im Header der HTTP-Antwort UTF-8 als Zeichensatz angegeben wird:
AddDefaultCharset UTF-8
MySQL
In der /etc/my.cnf
init-connect='SET NAMES utf8'
Bei jeder Verbindungsaufnahme (bspw. mysql_connect() via PHP) wird der Zeichensatz der ausgelieferten Daten damit auf UTF-8 geschaltet.
Selbstverständlich muss man aber immer noch aufpassen, in welchem Zeichensatz man Datenbank-Dumps exportiert und wieder einspielt ...
Labels: Apache, LAMP, Linux, Mac, MySQL, PHP, Web, Web-Entwicklung
Mittwoch, Mai 28, 2008
Vom Hammer und den Nägeln
Some of the largest sites on the internet -- sites you probably interact with on a daily basis -- are written in PHP. If PHP sucks so profoundly, why is it powering so much of the internet?
The only conclusion I can draw is that building a compelling application is far more important than choice of language.
Quelle: Coding Horror: PHP Sucks, But It Doesn't Matter
Wie sagt ein Kollege immer so schön? Es kommt nicht auf die Grösse des Hammers drauf an, sondern wie man damit nagelt ... Übertragen wohl: Es kommt nicht auf die Grösse des Hammers drauf an, sondern auf das, Haus, das man zusammennagelt. Oder so ...
Labels: PHP, Programmierung, Web
Sonntag, Mai 11, 2008
MySQL INSERTs und UPDATEs mit Subselects
Kürzlich durfte ich nachträglich zwei Datenbanktabelle normalisieren. Obwohl ich längst von den Subselect-Fähigkeiten von MySQL wusste, hatte ich es bis dato noch nie ausprobiert. Wie es sich herausgestellt hat, ist das Prozedere deutlich einfacher, als ich es mir erträumt hatte.
Rubriken neu in separater Tabelle
Die Tabelle daten enthält bspw. Adressen, die jeweils einer bestimmten Rubrik zugewiesen sind. Selbstverständlich kann man die Rubrik in dieselbe Tabelle hardcoden - doch deutlich hübscher ist es, die Rubrik in eine eigene Tabelle auszulagern und diese mittels eines Foreign Keys zu verknüpfen. Einer der Vorteile: Muss der Name einer Rubrik angepasst werden, geschieht dies an einem einzigen Ort, die Änderung wird aber gleich für alle Adressen übernommen.
Folgender Befehl nahm die hardcodierten Rubriken und fügte diese (jeweils einmal!) in die Tabelle rubriken ein:
INSERT INTO daten_rubriken (rubrik) SELECT DISTINCT(rubrik) FROM daten
Wichtig ist die SELECT ... Klausel - bis dahin handelt es sich beim SQL-Query um einen ganz normalen INSERT. DISTINCT bewirkt, dass der Rubrikennamen nur einmal ausgelesen wird (es können ja dutzende oder tausende Einträge dieselbe Rubrik haben).
Rubriken-IDs in Ursprungstabelle
Nachdem wir also nun die Rubriken in eine eigene Tabelle ausgelagert haben, möchten wir diese wieder mit der Ursprungstabelle verknüpfen. Hierzu verknüpfen wir die Textfelder der Ursprungs- mit der Rubriken-Tabelle und fügen in die Ursprungstabelle in ein neu erstelltes Feld die ID der Rubrik ein:
UPDATE daten d SET d.`daten_rubriken-id` = (SELECT r.id FROM daten_rubriken r WHERE r.rubrik = s.rubrik)
Labels: MySQL, PHP, Web-Entwicklung
Dienstag, November 06, 2007
Mediawiki mit kurzen und schönen URLs
Mediawiki kann über äusserst hässliche URLs wie http://wiki.domain.tld/index.php?title=Startseite oder aber über kurze und schöne URLs in der Form http://wiki.domain.tld/Startseite angesprochen werden.
Wie? Folgende Kurzanleitung soll mir (und allen anderen Leuten da draussenTM) für zukünftige Installationen als Gedankenstütze dienen:
.htaccess
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.+) /index.php?title=$1 [L]
LocalSettings.php
$wgArticlePath = "/$1"; $wgUsePathInfo = false;
Übrigens: Wenn ich in solchen Momenten den Code dieses Wiki-Systems näher betrachte, frage mich immer wieder, wie Wikipedia mit einem solchen Murks gross werden konnte.
Welches Wiki überzeugt?
Auf der Arbeit an der Uni verwende ich TWiki (Kurzkritik: Perl *wäh*, simple Dateistruktur, GUI unübersichtlich); auf der Arbeit in der Privatwirtschaft Atlassian Confluence. Dieses gefällt mir von all den Wikis da draussen bisher am meisten. Leider kostet es auch einen saftigen Batzen :-/
Dienstag, September 11, 2007
PHP5: 2006-13-01 wird nicht mehr akzeptiert
Als ich noch ein junger, unerfahrener PHP-"Programmierer" war (ich brauchte das Geld!), kamen ab und zu solche Konstrukte heraus:
$str_next_month = strtotime($arr_date["year"] . "-" . ($arr_date["mon"] + 1) . "-" . "01");
Das Script nimmt in Kauf, dass bei nicht sachgemässer Handhabung der Wert für den Monat auch einmal 13 betragen kann. In PHP4 meckerte strtotime() nicht gross um und rechnete das Datum kommentarlos auf den auf Dezember folgenden Januar um.
Nun, seit kurzem läuft PHP 5.2.2 auf meinem Entwicklungsserver (Liip und Hostpoint haben das ihre dazu beigetragen) - und bisher hielt sich mein Code tapfer. Bis heute abend.
Zu spüren bekam mein Server diese Fehlprogrammierung ausgerechnet in einer schicken while-Schleife (zu meiner Rehabilitierung die einzige in den knapp 1000 Zeilen PHP-Code). 120 Sekunden lang eine CPU-Auslastung von 100% - yippie! Multipliziert mit den Anzahl Versuchen, den Fehler einzugrenzen ... Heute wurden einige Elektronen zusätzlich verbraten und Schaltungen überbeansprucht.
Was lernen wir daraus?
- Programmiere keinen Scheiss zusammen! (Y2k reloaded?)
- Wenn du while-Schleifen schon nicht vermeiden kannst, baue wenigstens Sicherungen ein, z.B. ganz stümperhaft in Form eines Counters
Labels: PHP, Programmierung
Donnerstag, August 30, 2007
Danke Hostpoint! (oder: MySQL Suchen-und-Ersetzen)
Dank dem Wechsel von PHP4 auf PHP5 bei meinem für Kunden bevorzugten Hoster musste ich bei einigen Web-Sites folgende Änderungen vornehmen:
UPDATE `smt_content` SET source_de = REPLACE( source_de, 'PHP_SELF', 'ORIG_SCRIPT_NAME' )
Das Problem scheint daher zu rühren, dass PHP5 als CGI ausgeführt wird und einige Werte von $_SERVER nicht oder falsch zu setzen scheint. Mit ORIG_SCRIPT_NAME erhalte ich auf jeden Fall wieder den vom Web-Server ausgesehenen WWW-Pfad zum entsprechenden Script.
ACHTUNG
Natürlich erst, nachdem ich einen Dump der entsprechenden Tabelle gezogen hatte (ein Typo, und flutsch sind alle Seiteninhalte nach /dev/null unterwegs).
Nachtrag
Im Oktober 2007 wurde ein weiterer Wechsel nötig. Neu heisst die gesuchte Variable SCRIPT_NAME:
UPDATE `smt_content` SET source_de = REPLACE( source_de, 'PHP_SELF', 'SCRIPT_NAME' )
Dienstag, Juni 12, 2007
PHP aussagekräftiger benchmarken
In einer vor einigen Jahren programmierten Web-Applikation (ANIBILL; Übernachtungsstatistik und Rechnungsstellung für einen Tierstall) gab es vor einigen Wochen gewisse Performance-Engpässe, die sich in einer äusserst schleppenden Antwortzeit äusserten. Nach einigen ersten Untersuchungen isolierte ich MySQL (respektive unoptimierte Queries) als Ursache der Probleme.
Die richtige Vorgehensweise
Doch was nun? Klar konnte ich nun temporäre Änderungen an der Applikation auf einem Test-Server vornehmen und hoffen, dass ich den Fehler reproduzieren konnte. Oder aber: Ich änderte den Code dermassen, dass die Applikation künftig bei jedem Aufruf Zeitmessungen vornahm, mit denen ich ein aussagekräftigeres Bild erhielt. PHP-Programmierer werden es zu schätzen wissen, wenn sie Optimierungen auf Grund von 1'000 Messungen anstelle von 1-2 Probeläufen vornehmen können.
Die Lösung
Heraus kamen zwei Funktionen: anibill__get_db_data() und anibill__dump_microtime(). Durch die erste Funktion werden alle SQL-Queries "kanalisiert", die mit SELECTs arbeiten. Zur Beruhigung der versierten Leser: Diese Funktion war seit Beginn der Applikation vorhanden, ich musste also nicht 1'000 Codezeilen nach mysql_query() durchstrählen ...
Innerhalb dieser Funktion messe ich mittels microtime(), wie lange mysql_query() zum Ausführen meines SQL-Queries benötigt (und nur das, der Rest interessiert mich nicht). Bevor ich die aus der Datenbank gelesenen Daten zurückgebe, rufe ich die Benchmark-Funktion anibill__dump_microtime() auf, die mir einerseits die Laufzeit des Queries als auch gleich die aufrufende Funktion in eine Datenbank-Tabelle speichert. Bis zu diesem Zeitpunkt kannte ich die PHP-Funktion debug_backtrace() nicht - dabei ist sie äusserst mächtig. Ihre Ausgabe führt genau Buch, welche Funktionen seit dem Aufruf des Scripts ausgeführt wurden. Und nicht nur das - sogar die Zeilennummer des die Funktion enthaltenden PHP-Scripts ist angegeben. Ein Traum!
function anibill__get_db_data($str_sql_query,$bol_die_on_err = TRUE) {
...
// Benchmark current MySQL query
$arr_time[0] = microtime();
$obj_db_data = mysql_query($str_sql_query,$res_db_conn);
$arr_time[1] = microtime();
...
// Store benchmark to database
anibill__dump_microtime($arr_time,$str_sql_query);
return $arr_db_data;
}
function anibill__dump_microtime($arr_time,$str_sql_query) {
...
// Still using PHP4 and therefore wrestling with microtime()'s format
$arr_start = explode(" ", $arr_time[0]);
$arr_end = explode(" ", $arr_time[1]);
$int_seconds = $arr_end[1] - $arr_start[1];
$flo_microseconds = $arr_end[0] - $arr_start[0];
$flo_runtime = $int_seconds + $flo_microseconds;
// Which function called anibill__get_db_data() in the first place?
$arr_debug = debug_backtrace();
if(isset($arr_debug[2]['function']))
$str_function = $arr_debug[2]['function'];
else
$str_function = NULL;
if(isset($arr_debug[1]['line']))
$str_line = $arr_debug[1]['line'];
else
$str_line = NULL;
...
return TRUE;
}
Ich möchte all die Christians, Andreas und Silvans da draussen um Gnade bitten - mein Coding-Stil wird wohl nicht der effektivste sein (zu viele Zeilen, zu viele Einzüge etc.), doch die Lesbarkeit liegt mir am Herzen. Und ja, von OOP keine Spur ...
Selbstverständlich generiert das Benchmarken und Backtracen einen gewissen Overhead, doch aus meiner Sicht ist die vorgestellte Methode geeignet, die Performance kleinerer Web-Applikationen mit wenigen gleichzeitigen Zugriffen zu messen - ohne kostenpflichtige Tools einzukaufen und grosse Profiling-Sessions zu starten.
Zum Schluss
Erfinde ich das Rad neu? Gibt es elegantere Lösungen? Die Kommentarfunktion ist ab sofort geöffnet.
Freitag, Mai 04, 2007
Wenn Smarty nicht schreiben will
Vor kurzem erhielt ich bei der Installation einer PHP-Web-Applikation folgende Fehlermeldung in die error.log geschrieben:
[client 0.0.0.0] PHP Warning: Smarty error: problem creating directory "/var/webs/smarty/templates_c/%%778/%%778656331" in /var/webs/smarty/Smarty.class.php on line 589, referer: http://www.server.tld/ [client 0.0.0.0] PHP Warning: Smarty error: problem writing '/var/webs/smarty/templates_c/%%778/%%778656331/error.tpl.php.' in /var/webs/smarty/Smarty.class.php on line 589, referer: http://www.server.tld/
Obwohl ich die Berechtigungen des übergeordneten Verzeichnisses auf rwxrwxrwx (chmod 777) gesetzt hatte, weigerte sich Smarty resp. PHP, einen neuen Unterordner zu erstellen.
Nach einigen Pröbeleien und Google-Suchen fand ich dann doch noch eine einleuchte Antwort auf die Ursache des Problems:
A: This is the problem with your hosting provider. The directories which are created by php modules, have 644 permissions by default. You cannot fix it.
Quelle: Smarty error
Am selben Ort ist ein Workaround beschrieben. Man bearbeite inc/smarty.inc.php und ändere folgende Konfigurationsvariable:
$this->use_sub_dirs = false;
Voilà! Nun funkioniert auch UCCASS 1.8.1 auf meinem Server.
Sonntag, März 11, 2007
TerminGenius!
Um Sitzungen auf der Arbeit besser planen zu können, habe ich mich vor einigen Jahren spontan dazu entschlossen, ein PHP-Script zu schreiben, das mir die Online-Termin-Umfrage ermöglicht (genannt "Terminfinder"). Mit der Zeit kamen einige Inputs von den Benutzern hinzu und mit Version 2.0beta wurde das Produkt in "TerminGenius!" umbenannt.
Auf Wunsch von Stefan Oberwahrenbrock habe ich meine OSS-Applikation TerminGenius! um eine wichtige Funktion erweitert: Zur Erleichterung des Entscheides wird nun zusammengezählt, wie viele Leute an einem bestimmten Tag können.
eMeidi.com - Quelloffene Software
Weiterhin viel Spass bei der Suche nach einem Termin, der allen passt *smile*
PS: Wer sich nicht mit der Installation von PHP-Scripts auf einem Web-Server herumschlagen will, benutze Doodle.
Abonnieren
