Hatte gerade das Problem, dass ich gerade die Zeiten eines "crontab"-Aufrufs verifizieren musste. Aufgrund der ganzen Parameter-Möglichkeiten gar nicht so einfach. Hab aber gerade eine Seite gefunden, auf der man einfach die Parameter eingeben kann und dann zurückbekommt, wann der Cronjob in der Zukunft aufgerufen wird.
Sehr nett:
http://www.hxpi.com/cron_sandbox.php
Posts mit dem Label Webworking werden angezeigt. Alle Posts anzeigen
Posts mit dem Label Webworking werden angezeigt. Alle Posts anzeigen
Donnerstag, 13. August 2009
Donnerstag, 16. Juli 2009
Update der Serpent Template Engine auf 1.2.3
Und wieder mal gabs ein Update auf eine neue Version. Verbessert habe ich unter anderem den Creole-Parser, der jetzt endlich sauber mit Nested Lists umgehen kann.
Außerdem ist es jetzt möglich, die Standard-PHP-Tags ("<?php", "<?", "<?=") als Delimiter zu verwenden. OK, was es vorher auch schon, aber jetzt sind auch die Punkt-Syntax und Mappings kein Problem mehr. Und die Short Tags werden automatisch zu langen Tags umgebaut, was nützlich ist, falls die short tags in der php.ini ausgeschaltet wurden.
Serpent - PHP Template Engine
http://code.google.com/p/serpent-php-template-engine/
Außerdem ist es jetzt möglich, die Standard-PHP-Tags ("<?php", "<?", "<?=") als Delimiter zu verwenden. OK, was es vorher auch schon, aber jetzt sind auch die Punkt-Syntax und Mappings kein Problem mehr. Und die Short Tags werden automatisch zu langen Tags umgebaut, was nützlich ist, falls die short tags in der php.ini ausgeschaltet wurden.
http://code.google.com/p/serpent-php-template-engine/
Sonntag, 10. Mai 2009
Neue Serpent-Version: 1.2.1
Eben gerade hab ich die neue Version (Version 1.2.1) der Serpent Template Engine als Download bei Google Code zur Verfügung gestellt.
Dieses ist ein Bugfix, der den Creole-Parser etwas verbessert, welcher mit der Version 1.2 eingeführt wurde. Creole ist eine Wiki-Syntax, die eingeführt wurde, um den Wildwuchs an Syntaxen etwas zu bereinigen. Creole wird mittlerweile von einer Vielzahl von Wikis verwendet und sollte beim Bau einer eigenen Wiki-Engine zumindest unterstützt werden. Der User wirds euch danken.
Serpent - PHP Template Engine
http://code.google.com/p/serpent-php-template-engine/
Dieses ist ein Bugfix, der den Creole-Parser etwas verbessert, welcher mit der Version 1.2 eingeführt wurde. Creole ist eine Wiki-Syntax, die eingeführt wurde, um den Wildwuchs an Syntaxen etwas zu bereinigen. Creole wird mittlerweile von einer Vielzahl von Wikis verwendet und sollte beim Bau einer eigenen Wiki-Engine zumindest unterstützt werden. Der User wirds euch danken.
http://code.google.com/p/serpent-php-template-engine/
Donnerstag, 9. April 2009
X11-Programme unter Windows mit PuTTY und XMing
Ja, die Headline dieses Artikels ist vielleicht etwas gewöhnungsbedürftig. Aber es geht um ein tolles Thema... fangen wir von vorn an:
Seit längerer Zeit suche ich einen guten Konsolen-Editor, mit dem meine PuTTY-Sessions nicht so nervig wie bisher sind. Mit Vi(m), Nano, Joe usw. konnte ich mich einfach nicht anfreunden, weil sie alle ihre eigenen Tastenkürzel haben und halt nicht die, die ich aus GUI-Oberflächen (egal ob Windows oder Linux) gewohnt bin. Jetzt bin ich aber gerade über die optimale Lösung gestolpert: XMing zusammen mit PuTTY.
XMing ist ein X-Server, mit dem man auf die X11-Programme eines Servers zugreifen kann. Ich kann also über PuTTY ganz normal eine Konsolen-Session führen, mit dem Unterschied, dass ich jetzt auch Dateien mit z.B. GEdit bearbeiten kann. Ich sehe dann unter Windows ein Fenster mit GEdit drin. Genial. Selbst Gimp und OpenOffice liefen problemlos.
Hier die Anleitung:
Schon klappt es. Auf der XMing-Seite sieht man auch ein paar Screenshots, die Lust auf mehr machen.
Ach ja: Damit eure Konsole nicht blockiert ist, hängt ein & an den Befehl heran, z.B.
gedit dummy.txt &
Und Nautilus solltet ihr so starten:
nautilus --no-desktop &
Seit längerer Zeit suche ich einen guten Konsolen-Editor, mit dem meine PuTTY-Sessions nicht so nervig wie bisher sind. Mit Vi(m), Nano, Joe usw. konnte ich mich einfach nicht anfreunden, weil sie alle ihre eigenen Tastenkürzel haben und halt nicht die, die ich aus GUI-Oberflächen (egal ob Windows oder Linux) gewohnt bin. Jetzt bin ich aber gerade über die optimale Lösung gestolpert: XMing zusammen mit PuTTY.
XMing ist ein X-Server, mit dem man auf die X11-Programme eines Servers zugreifen kann. Ich kann also über PuTTY ganz normal eine Konsolen-Session führen, mit dem Unterschied, dass ich jetzt auch Dateien mit z.B. GEdit bearbeiten kann. Ich sehe dann unter Windows ein Fenster mit GEdit drin. Genial. Selbst Gimp und OpenOffice liefen problemlos.
Hier die Anleitung:
- XMing und XMing-portablePuTTY von der XMing Hauptseite herunterladen und installieren.
- Wie gewohnt eine PuTTY-Session starten mit einem kleinen Unterschied: Bei den Einstellungen unter "Connection > SSH > X11" muss "X11 forwarding" aktiviert sein.
- Auf dem Server muss X11 forwarding in "/etc/ssh/sshd.config" aktiviert sein. Bei mir (Ubuntu-Server) war es das standardmäßig, musste also nichts ändern. Falls man etwas ändern muss: SSH-Daemon oder Rechner neu starten.
Schon klappt es. Auf der XMing-Seite sieht man auch ein paar Screenshots, die Lust auf mehr machen.
Ach ja: Damit eure Konsole nicht blockiert ist, hängt ein & an den Befehl heran, z.B.
gedit dummy.txt &
Und Nautilus solltet ihr so starten:
nautilus --no-desktop &
phpThumb mit statisch kompiliertem Imagemagick
Hatte gerade das Problem, dass ich für einen Kunden eine Bilddatenbank aufsetzen sollte, die mit phpThumb läuft. Der Kunde wollte jetzt auch z.B. PSD-Dateien mit einer Vorschau angezeigt bekommen und somit musste natürlich auch ImageMagick auf dem Server liegen... tat es aber nicht... und war auch nicht nachzuinstallieren, weil es um einen Managed Server ging.
Allerdings geistert im Web eine statisch kompilierte Version 4.2.9 (uralt) herum, die eigentlich für Typo3 gedacht ist. Die kann man dann per FTP einfach hochladen, muss noch die Ausführungsrechte setzen und schon gehts... nur nicht mit PHPThumb. Hier sind ein paar kleine Änderungen nötig.
Zuerst muss der Pfad zum convert-Tool in der "phpThumb.config.php" angegeben werden. Einfach folgende Zeile suchen und anpassen:
$PHPTHUMB_CONFIG['imagemagick_path'] = 'path/to/imagemagick-4.2.9_i386-static/convert';
Dann muss die "phpthumb.class.php" leicht angepasst werden. Zuerst die Funktion "ImageMagickVersion". Da der reguläre Ausdruck in dieser Funktion nicht die Versionsnummer der statisch kompilierten IM-Version matcht, müssen wir da ein wenig nachhelfen. Am einfachsten (und schmutzigsten) ist es, direkt eine eigene Versionsnummer zurückzugeben:
// diese Zeile vor dem return am Ende hinzufügen
return '4.2.9 99/09/01';
return @$versionstring[intval($returnRAW)];
Jetzt erkennt phpThumb schon einmal, dass es eine gültige ImageMagick-Version gibt. Allerdings haben wir noch das Problem, dass es in dieser uralten Version noch nicht die "-resize" Parameter gab. Hierfür reicht es, in der Funktion "ImageMagickThumbnailToGD" die Zeile
$IMresizeParameter = 'resize';
durch
$IMresizeParameter = 'size';
zu ersetzen. Und schon läufts.
Allerdings geistert im Web eine statisch kompilierte Version 4.2.9 (uralt) herum, die eigentlich für Typo3 gedacht ist. Die kann man dann per FTP einfach hochladen, muss noch die Ausführungsrechte setzen und schon gehts... nur nicht mit PHPThumb. Hier sind ein paar kleine Änderungen nötig.
Zuerst muss der Pfad zum convert-Tool in der "phpThumb.config.php" angegeben werden. Einfach folgende Zeile suchen und anpassen:
$PHPTHUMB_CONFIG['imagemagick_path'] = 'path/to/imagemagick-4.2.9_i386-static/convert';
Dann muss die "phpthumb.class.php" leicht angepasst werden. Zuerst die Funktion "ImageMagickVersion". Da der reguläre Ausdruck in dieser Funktion nicht die Versionsnummer der statisch kompilierten IM-Version matcht, müssen wir da ein wenig nachhelfen. Am einfachsten (und schmutzigsten) ist es, direkt eine eigene Versionsnummer zurückzugeben:
// diese Zeile vor dem return am Ende hinzufügen
return '4.2.9 99/09/01';
return @$versionstring[intval($returnRAW)];
Jetzt erkennt phpThumb schon einmal, dass es eine gültige ImageMagick-Version gibt. Allerdings haben wir noch das Problem, dass es in dieser uralten Version noch nicht die "-resize" Parameter gab. Hierfür reicht es, in der Funktion "ImageMagickThumbnailToGD" die Zeile
$IMresizeParameter = 'resize';
durch
$IMresizeParameter = 'size';
zu ersetzen. Und schon läufts.
Dienstag, 7. April 2009
InnoDB und große Tabellen
Vor kurzem hatte ich die Aufgabe, ein Gewinnspiel umzusetzen, bei dem User sich pro E-Mail-Adresse einen Gewinncode zurückgeben lassen konnten. Die 7 Millionen Gewinncodes kamen dabei vom Kunden und waren alle unique. Jeder Code durfte dabei nur einmal ausgespielt werden. Um das sicherzustellen kamen nur Transaktionen in Frage. Somit war InnoDB Pflicht.
Ok, als erstes hab ich die beiden Tabellen erstellt:
--
-- Tabellenstruktur für Tabelle `codes`
--
CREATE TABLE `codes` (
`code` char(6) character set latin1 collate latin1_bin NOT NULL,
`user_id` int(11) default NULL,
PRIMARY KEY (`code`),
KEY `user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=FIXED;
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `users`
--
CREATE TABLE `users` (
`id` int(11) NOT NULL auto_increment,
`salutation` set('Herr','Frau') NOT NULL,
`firstname` char(50) NOT NULL,
`lastname` char(50) NOT NULL,
`street` char(100) NOT NULL,
`hnr` char(20) NOT NULL,
`zip` char(10) NOT NULL,
`city` char(100) NOT NULL,
`email` char(100) NOT NULL,
`created` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=FIXED AUTO_INCREMENT=1;
$data_file = '/tmp/codes.txt';
$db = 'otto_paf_mai2009';
$chunk_file = '/tmp/codes.part';
# split the file into small chunks to output a status report
$line = 0;
$buffer = '';
$handle = fopen ($data_file, 'r');
while (!feof($handle))
{
$line++;
$buffer .= fgets($handle);
if ($line%10000 === 0)
{
file_put_contents($chunk_file, $buffer);
$buffer = '';
# the data file has to have the same name as the table
# it has to be readable by the mysql server (especially the directory)
# mysqlimport [options] [db_name] [data_file]
shell_exec('mysqlimport -uroot --columns=code --verbose '.$db.' '.$chunk_file);
stdout($result);
stdout(number_format($line, 0, ',', '.'));
}
}
fclose ($handle);
function stdout($out){fwrite(STDOUT, $out."\n");}
function stderr($out){fwrite(STDERR, $out."\n");}
Interessant war hierbei schon, wie lange MySQL dafür braucht und wie tödlich ein Unique-Key (in diesem Fall der PRIMARY) sein kann. Der Import in MyISAM ohne Unique-Key dauerte eine halbe Minute. Der Import in InnoDB ohne Unique-Key etwa 5 Minuten. Und der Import in InnoDB MIT dem Unique-Key dauerte letztendlich 5,5 Stunden. Autsch! Allerdings war es ganz gut, dass ich den Unique-Key gesetzt habe, denn bei den ersten Daten des Kunden waren tatsächlich Dubletten in den Codes.
SELECT code
FROM codes
WHERE user_id IS NULL
ORDER BY RAND()
LIMIT 1
Dauert aber leider schon bei 600.000 Datensätzen über eine halbe Sekunde. Und das EXPLAIN macht alles klar. In der Spalte "extra" steht:
Using where; Using index; Using temporary; Using filesort
Das sieht übel aus.
Mein nächster Ansatz war, das ganze in zwei Abfragen auszulagern. Die erste sollte sich die Anzahl der gültigen Codes zurückliefern, die zweite schließlich mit einem zufälligen Offset beim LIMIT arbeiten, der halt zwischen 0 und der Anzahl der gültigen Codes liegen sollte. Das sah etwa so aus:
// get count of codes
$sql = $this->db->result("
SELECT count(*) as count
FROM codes
WHERE user_id IS NULL
");
$offset = rand(0, $sql['RESULT'][0]['count']);
// get code from db
$sql = $this-db->Result("
SELECT code
FROM codes
WHERE user_id IS NULL
LIMIT $offset, 1
");
$code = $sql['RESULT'][0]['code'];
Schon die erste Abfrage dauerte im Schnitt 1,7 Sekunden. Aber warum? Die Antwort findet sich hier: http://www.mysqlperformanceblog.com/2006/12/01/count-for-innodb-tables/
Ein COUNT(*) auf viele Datensätze ist bei InnoDB einfach höllisch langsam. Die nächste Idee war hierbei, die erste Abfrage umzudrehen. Da ich nämlich weiß, wieviele Keys in der DB sind, brauche ich nur die user_ids zählen und von der Gesamtzahl abzuziehen. Das war auch tatsächlich sehr schnell. Selbst die zweite Abfrage...
ABER HALT...
... anscheinend nur bei der ersten Abfrage. Irgendwie schwanken die Zeiten zwischen 0,0003 Sekunden und 2,5 Sekunden. Nach ein bisschen Herumprobieren kam auch hier der Übertäter zum Vorschein: Das LIMIT. Ist der Offset klein, kommt das Ergebnis sehr schnell, ist er groß, dauert es ewig lang. Mist.
Mein derzeitiger Ansatz ist mehr ein Workaround. Da ich weiß, dass die Codes mit einem Klein- oder Großbuchstaben anfangen, wähle ich einfach einen aus und lasse mir nur Codes zurückgeben, die halt diesen am Anfang stehen haben:
$values = array_merge( range('a', 'z'), range('A', 'Z') );
$char = $values[ rand(0, count($values)-1 ) ];
$sql = $this-db->Result("
SELECT code
FROM codes
FORCE INDEX(PRIMARY)
WHERE code LIKE '".$char."%'
AND user_id IS NULL
LIMIT 1
");
Interessant war hierbei das FORCE INDEX. Ohne wollte MySQL lieber den Index für die user_id verwenden als den PRIMARY KEY. Verrückt. Allerdings ist die endgültige Lösung mit 0,007 Sekunden recht schnell. Allerdings bekomme ich nicht wirklich zufällige Ergebnisse heraus.
Wer also eine bessere Lösung kennt oder eine andere Idee hat: Immer her damit!
Ok, als erstes hab ich die beiden Tabellen erstellt:
--
-- Tabellenstruktur für Tabelle `codes`
--
CREATE TABLE `codes` (
`code` char(6) character set latin1 collate latin1_bin NOT NULL,
`user_id` int(11) default NULL,
PRIMARY KEY (`code`),
KEY `user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=FIXED;
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `users`
--
CREATE TABLE `users` (
`id` int(11) NOT NULL auto_increment,
`salutation` set('Herr','Frau') NOT NULL,
`firstname` char(50) NOT NULL,
`lastname` char(50) NOT NULL,
`street` char(100) NOT NULL,
`hnr` char(20) NOT NULL,
`zip` char(10) NOT NULL,
`city` char(100) NOT NULL,
`email` char(100) NOT NULL,
`created` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=FIXED AUTO_INCREMENT=1;
Problem 1: Wie importiert man 7 Millionen Datensätze möglichst schnell?
Einzelne Queries sind langsam. Mehrere Queries per Transaktion zu bündeln ist ein wenig schneller. Das schnellste aber scheint LOAD DATA zu sein, welches auch über das Shell-Tool mysqlimport zu verwenden ist. Da man aber bei mysqlimport keinen Fortschritt ausgeworfen bekommt, hab ich fix ein Shell-Skript zusammengeschrieben, welches alle 10.000 Zeilen eine Meldung auswirft.$data_file = '/tmp/codes.txt';
$db = 'otto_paf_mai2009';
$chunk_file = '/tmp/codes.part';
# split the file into small chunks to output a status report
$line = 0;
$buffer = '';
$handle = fopen ($data_file, 'r');
while (!feof($handle))
{
$line++;
$buffer .= fgets($handle);
if ($line%10000 === 0)
{
file_put_contents($chunk_file, $buffer);
$buffer = '';
# the data file has to have the same name as the table
# it has to be readable by the mysql server (especially the directory)
# mysqlimport [options] [db_name] [data_file]
shell_exec('mysqlimport -uroot --columns=code --verbose '.$db.' '.$chunk_file);
stdout($result);
stdout(number_format($line, 0, ',', '.'));
}
}
fclose ($handle);
function stdout($out){fwrite(STDOUT, $out."\n");}
function stderr($out){fwrite(STDERR, $out."\n");}
Interessant war hierbei schon, wie lange MySQL dafür braucht und wie tödlich ein Unique-Key (in diesem Fall der PRIMARY) sein kann. Der Import in MyISAM ohne Unique-Key dauerte eine halbe Minute. Der Import in InnoDB ohne Unique-Key etwa 5 Minuten. Und der Import in InnoDB MIT dem Unique-Key dauerte letztendlich 5,5 Stunden. Autsch! Allerdings war es ganz gut, dass ich den Unique-Key gesetzt habe, denn bei den ersten Daten des Kunden waren tatsächlich Dubletten in den Codes.
Problem 2: Wie gibt man einen zufälligen Code aus?
Der offensichtlichste und verbreitetste Ansatz ist:SELECT code
FROM codes
WHERE user_id IS NULL
ORDER BY RAND()
LIMIT 1
Dauert aber leider schon bei 600.000 Datensätzen über eine halbe Sekunde. Und das EXPLAIN macht alles klar. In der Spalte "extra" steht:
Using where; Using index; Using temporary; Using filesort
Das sieht übel aus.
Mein nächster Ansatz war, das ganze in zwei Abfragen auszulagern. Die erste sollte sich die Anzahl der gültigen Codes zurückliefern, die zweite schließlich mit einem zufälligen Offset beim LIMIT arbeiten, der halt zwischen 0 und der Anzahl der gültigen Codes liegen sollte. Das sah etwa so aus:
// get count of codes
$sql = $this->db->result("
SELECT count(*) as count
FROM codes
WHERE user_id IS NULL
");
$offset = rand(0, $sql['RESULT'][0]['count']);
// get code from db
$sql = $this-db->Result("
SELECT code
FROM codes
WHERE user_id IS NULL
LIMIT $offset, 1
");
$code = $sql['RESULT'][0]['code'];
Schon die erste Abfrage dauerte im Schnitt 1,7 Sekunden. Aber warum? Die Antwort findet sich hier: http://www.mysqlperformanceblog.com/2006/12/01/count-for-innodb-tables/
Ein COUNT(*) auf viele Datensätze ist bei InnoDB einfach höllisch langsam. Die nächste Idee war hierbei, die erste Abfrage umzudrehen. Da ich nämlich weiß, wieviele Keys in der DB sind, brauche ich nur die user_ids zählen und von der Gesamtzahl abzuziehen. Das war auch tatsächlich sehr schnell. Selbst die zweite Abfrage...
ABER HALT...
... anscheinend nur bei der ersten Abfrage. Irgendwie schwanken die Zeiten zwischen 0,0003 Sekunden und 2,5 Sekunden. Nach ein bisschen Herumprobieren kam auch hier der Übertäter zum Vorschein: Das LIMIT. Ist der Offset klein, kommt das Ergebnis sehr schnell, ist er groß, dauert es ewig lang. Mist.
Mein derzeitiger Ansatz ist mehr ein Workaround. Da ich weiß, dass die Codes mit einem Klein- oder Großbuchstaben anfangen, wähle ich einfach einen aus und lasse mir nur Codes zurückgeben, die halt diesen am Anfang stehen haben:
$values = array_merge( range('a', 'z'), range('A', 'Z') );
$char = $values[ rand(0, count($values)-1 ) ];
$sql = $this-db->Result("
SELECT code
FROM codes
FORCE INDEX(PRIMARY)
WHERE code LIKE '".$char."%'
AND user_id IS NULL
LIMIT 1
");
Interessant war hierbei das FORCE INDEX. Ohne wollte MySQL lieber den Index für die user_id verwenden als den PRIMARY KEY. Verrückt. Allerdings ist die endgültige Lösung mit 0,007 Sekunden recht schnell. Allerdings bekomme ich nicht wirklich zufällige Ergebnisse heraus.
Wer also eine bessere Lösung kennt oder eine andere Idee hat: Immer her damit!
Samstag, 7. März 2009
Neue Serpent Version: 1.1 RC1
Eben gerade hab ich die neue Version (Version 1.1 RC1) der Serpent Template Engine als Download bei Google Code zur Verfügung gestellt.
Wie man an der Versionsnummer sehen kann, ist es ein Release Candidate, der also schon sehr stabil läuft.
Neue Features sind eine einfachere Initialisation, ein neues Plugin-System (welches es noch einfacher macht, die Template-Engine zu erweitern) und die Möglichkeit, auch den Compiler on the fly zu wechseln (weil dieser selbst jetzt auch als Plugin eingebunden ist). Es liegt jetzt auch ein Compiler für die Wiki-ähnliche Markdown-Syntax dabei.
Die Dokumentation ist natürlich schon auf dem neuesten Stand.
Serpent - PHP Template Engine
http://code.google.com/p/serpent-php-template-engine/
Wie man an der Versionsnummer sehen kann, ist es ein Release Candidate, der also schon sehr stabil läuft.
Neue Features sind eine einfachere Initialisation, ein neues Plugin-System (welches es noch einfacher macht, die Template-Engine zu erweitern) und die Möglichkeit, auch den Compiler on the fly zu wechseln (weil dieser selbst jetzt auch als Plugin eingebunden ist). Es liegt jetzt auch ein Compiler für die Wiki-ähnliche Markdown-Syntax dabei.
Die Dokumentation ist natürlich schon auf dem neuesten Stand.
Serpent - PHP Template Engine
http://code.google.com/p/serpent-php-template-engine/
Dienstag, 10. Februar 2009
Serpent - PHP Template Engine
Nach jahrelangem Durchforsten des Webs nach guten Template Engines, die leider nicht das boten, was ich benötige, bin ich jetzt endlich meiner Standard-Engine Smarty abtrünnig geworden und habe in den letzten Wochen meine eigene Template Engine umgesetzt: Serpent.
Und, nein, ich finde Smarty ganz und gar nicht schlecht. Ich werde es auch wieder einsetzen, wenn ich das Hauptfeature "template security" brauchen sollte, aber bis dahin werd ich wohl recht glücklich mit meiner Engine, die genau die für mich sinvoll wirkenden Features integriert.
Soeben habe ich die erste beta-Version veröffentlicht und wäre natürlich glücklich, wenn sie jemand mal ausprobieren und sein Feedback posten bzw. in den "Issues" bei Google Code einstellen würde.
Auf der Haben-Seite steht, dass die Template-Sprache PHP ist, was es natürlich extrem flexibel macht. Damit ist es ähnlich wie Savant3, hat aber die Vorteile, dass es nicht auf short_tags aufbaut, um eine kurze Syntax hinzukriegen.
Desweiteren untersützt Serpent die von Django und Dwoo bekannte Template-Vererbung, die von Smarty entliehende Punkt-Syntax für Arrays, Funktionen-Mapping für weniger Schreibarbeit und Resourcen-Handler, weil Templates ja nicht immer aus dem Dateisystem kommen müssen (beim CMS z.B. auch aus einer Datenbank).
E_STRICT-Kompatibilität, gute Objektorientierung und verdammt gute Performance sind selbstverständlich, oder?!
Um ein genaueres Bild von Serpent zu bekommen, empfehle ich, einen Blick auf die Overview-Seite zu werfen:
http://code.google.com/p/serpent-php-template-engine/wiki/Overview
Dann viel Spaß beim Testen.
Serpent - PHP Template Engine
http://code.google.com/p/serpent-php-template-engine/
Und, nein, ich finde Smarty ganz und gar nicht schlecht. Ich werde es auch wieder einsetzen, wenn ich das Hauptfeature "template security" brauchen sollte, aber bis dahin werd ich wohl recht glücklich mit meiner Engine, die genau die für mich sinvoll wirkenden Features integriert.
Soeben habe ich die erste beta-Version veröffentlicht und wäre natürlich glücklich, wenn sie jemand mal ausprobieren und sein Feedback posten bzw. in den "Issues" bei Google Code einstellen würde.
Was unterscheidet Serpent von anderen Template Engines?
Es integriert zum einen keine Template-Sicherheit, weil ich sie in den letzten 10 Jahren nicht gebraucht habe. Und zum anderen gibt es kein eingebautes Caching-System, weil das Caching des Outputs in vielen Frameworks über das View des MVCs geregelt wird. Und da eine Template Engine nur EINE Möglichkeit der Ausgabe darstellt (neben XML, JSON, CSV usw.) ist das auch nicht Aufgabe der Engine.Auf der Haben-Seite steht, dass die Template-Sprache PHP ist, was es natürlich extrem flexibel macht. Damit ist es ähnlich wie Savant3, hat aber die Vorteile, dass es nicht auf short_tags aufbaut, um eine kurze Syntax hinzukriegen.
Desweiteren untersützt Serpent die von Django und Dwoo bekannte Template-Vererbung, die von Smarty entliehende Punkt-Syntax für Arrays, Funktionen-Mapping für weniger Schreibarbeit und Resourcen-Handler, weil Templates ja nicht immer aus dem Dateisystem kommen müssen (beim CMS z.B. auch aus einer Datenbank).
E_STRICT-Kompatibilität, gute Objektorientierung und verdammt gute Performance sind selbstverständlich, oder?!
Um ein genaueres Bild von Serpent zu bekommen, empfehle ich, einen Blick auf die Overview-Seite zu werfen:
http://code.google.com/p/serpent-php-template-engine/wiki/Overview
Dann viel Spaß beim Testen.
Serpent - PHP Template Engine
http://code.google.com/p/serpent-php-template-engine/
Montag, 9. Februar 2009
Reguläre Ausdrücke online testen
Wer kennt das nicht? Hin und wieder muss man einen regulären Ausdruck testen, aber wer hat schon Lust, sich dafür etwas zusammenzuschreiben? Dank des genialen Online-Tools "Regex Tester V3" auch gar nicht mehr nötig.
Nicht nur, dass man wirklich alle in PHP verbauten Regex-Funktionen komfortabel testen kann. Man kann sogar seine Test-Umgebungen inkl. aller Parameter speichern und zusätzlich einen PHP-Beispiel-Code generieren lassen, den man hervorragend z.B. in Foren posten kann.
Ein wirklich starkes Tool.
Regex Tester V3
http://www.regex-tester.de/regex.html
Nicht nur, dass man wirklich alle in PHP verbauten Regex-Funktionen komfortabel testen kann. Man kann sogar seine Test-Umgebungen inkl. aller Parameter speichern und zusätzlich einen PHP-Beispiel-Code generieren lassen, den man hervorragend z.B. in Foren posten kann.
Ein wirklich starkes Tool.
Regex Tester V3
http://www.regex-tester.de/regex.html
Montag, 1. Dezember 2008
SEO: Doppelten Content vermeiden
Alles klar? Wer jetzt noch nicht Bescheid weiß, sollte besser weiterlesen. Doppelter Content sind Seiten, die unter verschiedenen URLs auftauchen, aber ansonsten denselben Inhalt bieten. Google mag das leider gar nicht, und so kann auch schnell mal das Ranking der eigenen Seite schlechter werden, wenn z.B. die Startseite unter http://webboarder.de, http://www.webboarder.de oder http://webboarder.de/index.htm zu finden ist.
Für mich die wertvollsten Tipps sind hier:
RewriteEngine On
RewriteCond %{HTTP_HOST} ^www\.webboarder\.de$ [NC]
RewriteRule ^(.*)$ http://webboarder.de/$1 [R=301,L]
Für mich die wertvollsten Tipps sind hier:
- Konsistent verlinken: Nicht hier mal so und auf der nächsten Seite vollkommen anders. Immer dieselbe URL für eine Verlinkung verwenden.
- EINE Domain verwenden: Entweder mit Subdomain "www" oder ohne. Aber entscheiden müsst ihr euch.
RewriteEngine On
RewriteRule ^(.*)$ http://webboarder.de/$1 [R=301,L]
Tipps zu dem Thema von Google:
http://www.google.com/support/webmasters/bin/answer.py?hl=en&answer=66359
Sonntag, 30. November 2008
Errors in Exceptions umwandeln (2)
Da war ich wohl in meinem letzten Artikel "Errors in Exceptions umwandeln" etwas voreilig. Durch einen Workaround, den ich ausversehen noch bei mir eingebaut hatte, wirkte es auf mich so, als wäre der Backtrace der ErrorException richtig. Dem ist aber leider nicht so. Nach wie vor beinhaltet der erste Eintrag die Zeile, in der die ErrorException geworfen wird.
Und wie ich in den Kommentaren im PHP-Manual gelesen habe (und auch selber nachvollziehen konnte), gibt es auch noch einen PHP-5.2-Bug in der ErrorException, welche die Argumente im Backtrace durcheinanderwürfelt. Und dieser kann leider erst mit PHP 5.3 behoben werden. Na, klasse.
Bevor ihr jetzt den Workaround aus den Manual-Comments einbaut, bei dem ihr auch noch ständig die PHP-Version überprüfen müsst, empfehle ich, die ExceptionError-Klasse selbst durch Erweiterung der Exception-Klasse zu bauen. Sollte recht schnell gehen, beinhaltet den Fehler nicht, und man könnte automatisch den ersten Eintrag aus dem Backtrace herauswerfen, um sich um keinen Sonderfall im Exception-Handler kümmern zu müssen.
PHP Bugs: #45895: Exception in set_error_handler() messes up backtrace args
Und wie ich in den Kommentaren im PHP-Manual gelesen habe (und auch selber nachvollziehen konnte), gibt es auch noch einen PHP-5.2-Bug in der ErrorException, welche die Argumente im Backtrace durcheinanderwürfelt. Und dieser kann leider erst mit PHP 5.3 behoben werden. Na, klasse.
Bevor ihr jetzt den Workaround aus den Manual-Comments einbaut, bei dem ihr auch noch ständig die PHP-Version überprüfen müsst, empfehle ich, die ExceptionError-Klasse selbst durch Erweiterung der Exception-Klasse zu bauen. Sollte recht schnell gehen, beinhaltet den Fehler nicht, und man könnte automatisch den ersten Eintrag aus dem Backtrace herauswerfen, um sich um keinen Sonderfall im Exception-Handler kümmern zu müssen.
PHP Bugs: #45895: Exception in set_error_handler() messes up backtrace args
Donnerstag, 27. November 2008
Clientside Cache Control
Auf techblog.tilllate.com gab es vor kurzer Zeit einen recht interessanten Ansatz zur Cache-Kontrolle durch den User. Dieses ist eine deutsche Erklärung dazu, die noch einen kleinen Schritt weiter geht.
Jeder Webworker hat wohl schon mal Caching in seine Applikationen eingebaut. Spätestens, wenn der Server fast am Krepieren ist und der Kunde Sturm klingelt, weil seine Website nicht mehr zu erreichen ist, ist es soweit. Die mit Abstand häufigste Caching-Form ist dabei wohl das serverseitige Cachen, bei dem die Website-Administratoren dann selbst entscheiden, wie oft sich ein Cache erneuert. Leider sorgt das bei den Besuchern gern mal für Unmut, wenn z.B. der User in dem seiteninternen Messaging-System nicht mehr sieht, ob er schon eine neue Nachricht bekommen hat.
Was tun in einem solchen Fall? Man möchte die User ja nicht vergraulen. Andererseits darf der Server auch nicht in die Knie gehen.
Die Lösung: den User selbst dafür sorgen lassen, dass sich der Server-Cache zur richtigen Zeit erneuert.
Beim Check sieht man, dass entweder der Header cache-control (HTTP/1.1) oder pragma (HTTP/1.0) mitgeschickt wird. Beide entweder mit dem Wert no-cache oder max-age=0.
Wie in dem Original-Bericht habe ich mal überprüft, bei welchen Browsern verwertbare Request Header mitgeschickt werden, wobei ich ein paar Tastenkombinationen mehr unter die Lupe genommen habe. Lustigerweise kommen bei mir zum Originalartikel abweichende Ergebnisse heraus:
X: cache-control oder pragma wurden mitgeschickt
O: keine zusätzlicher Header wurde mitgeschickt
-: Die Tastenkombination ist in dem Browser nicht möglich
Sehr viel mehr Traffic dürfte für den Server im Normalfall nicht herauskommen, weil die User normalerweise nur von Seite zu Seite springen und daher ziemlich selten die benötigten Header absenden.
Ich werde das wohl in Kürze einbauen, weil es auch für mich als Entwickler eine Erleichterung ist, den Cache während der Entwicklung einfach per Tastendruck zu umgehen.
Der Originalartikel: http://techblog.tilllate.com/2008/11/14/clientside-cache-control/
RFC2616: HTTP/1.1: Header Field Definitions
http://www.requestheaders.com
Jeder Webworker hat wohl schon mal Caching in seine Applikationen eingebaut. Spätestens, wenn der Server fast am Krepieren ist und der Kunde Sturm klingelt, weil seine Website nicht mehr zu erreichen ist, ist es soweit. Die mit Abstand häufigste Caching-Form ist dabei wohl das serverseitige Cachen, bei dem die Website-Administratoren dann selbst entscheiden, wie oft sich ein Cache erneuert. Leider sorgt das bei den Besuchern gern mal für Unmut, wenn z.B. der User in dem seiteninternen Messaging-System nicht mehr sieht, ob er schon eine neue Nachricht bekommen hat.
Was tun in einem solchen Fall? Man möchte die User ja nicht vergraulen. Andererseits darf der Server auch nicht in die Knie gehen.
Die Lösung: den User selbst dafür sorgen lassen, dass sich der Server-Cache zur richtigen Zeit erneuert.
Auf die Tastenkürzel kommt es an
Die meisten wissen, dass man z.B. im Firefox per Strg+Shift+R dafür sorgen kann, dass der Browser-eigene Cache übergangen wird. Weniger bekannt ist, dass in diesem Fall auch zusätzliche Request Header an den Server geschickt werden. Anschauen kann man dieses sehr gut, wenn man die verschiedenen Reload-Varianten mal auf requestheaders.com ausprobiert.Beim Check sieht man, dass entweder der Header cache-control (HTTP/1.1) oder pragma (HTTP/1.0) mitgeschickt wird. Beide entweder mit dem Wert no-cache oder max-age=0.
Wie in dem Original-Bericht habe ich mal überprüft, bei welchen Browsern verwertbare Request Header mitgeschickt werden, wobei ich ein paar Tastenkombinationen mehr unter die Lupe genommen habe. Lustigerweise kommen bei mir zum Originalartikel abweichende Ergebnisse heraus:
| Browser | URL-Enter | F5 | Strg+F5 | Strg+R | Strg+Shift+R |
|---|---|---|---|---|---|
| Firefox 3.0.4 | O | O | X | O | X |
| IE 7 | O | O | X | O | - |
| IE 6 | O | O | O | O | - |
| Safari 3.1.2 (Windows) | O | X | - | X | - |
| Opera 9.51 | O | X | - | X | - |
| Chrome | X | X | X | X | - |
X: cache-control oder pragma wurden mitgeschickt
O: keine zusätzlicher Header wurde mitgeschickt
-: Die Tastenkombination ist in dem Browser nicht möglich
Fazit:
Bei jedem Browser kommt unter bestimmten Bedingungen ein gültiger Wert heraus. Man könnte es also dem User überlassen, ob ein serverseitiger Cache invalide wird. Da die Befehle ja auch nur gesendet werden, wenn ein User explizit einen Reload anordnet, während er die Seite eh schon auf dem Schirm hat, kann das auch als forcierter und gewollter Reload betrachtet werden. Der User will also schauen, ob es eine neue Variante gibt.Sehr viel mehr Traffic dürfte für den Server im Normalfall nicht herauskommen, weil die User normalerweise nur von Seite zu Seite springen und daher ziemlich selten die benötigten Header absenden.
Ich werde das wohl in Kürze einbauen, weil es auch für mich als Entwickler eine Erleichterung ist, den Cache während der Entwicklung einfach per Tastendruck zu umgehen.
Der Originalartikel: http://techblog.tilllate.com/2008/11/14/clientside-cache-control/
RFC2616: HTTP/1.1: Header Field Definitions
http://www.requestheaders.com
Dienstag, 25. November 2008
Errors in Exceptions umwandeln
Exceptions sind eine feine Sache. Sie kommen jedem faulen Programmierer entgegen, der keine Lust hat, jeden möglichen Fehlerfall einzeln abzuhandeln. Dumm nur, dass PHP-Funktionen aufgrund von Rückwarts-Kompatibilität immer noch Fehler statt Exceptions werfen.
Einer eher bekanntere Methode ist, einen Errorhandler zu schreiben, der nur die Aufgabe hat, eine Exception zu werfen. Das Problem: Der Backtrace, den man ja mit $e->getTrace() erhält, beginnt dummerweise mit der Zeile, in der die Exception geworfen wurde, und nicht mit der ders eigentliche Fehlers. Deshalb kann man dummerweise nicht den gleichen Top-Level-Exception-Handler verwenden, den man für Standard-Exceptions verwendet.
Eher unbekannt ist, dass es in PHP schon eine erweitere Exception namens ErrorException gibt. Mit folgendem simplen Code ist es problemlos möglich, Fehler auf Exceptions zu mappen:
function exception_error_handler($errno, $errstr, $errfile, $errline ) {
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
}
set_error_handler("exception_error_handler");
Wer das ganze jetzt ein wenig aufmöbeln will, kann in diesem error handler jetzt noch den error_reporting Level abfragen, damit nur Fehler Exceptions werfen, die das auch wirklich sollen.
ErrorException im PHP Manual
Exceptions im PHP Manual
error_reporting() im PHP Manual
Einer eher bekanntere Methode ist, einen Errorhandler zu schreiben, der nur die Aufgabe hat, eine Exception zu werfen. Das Problem: Der Backtrace, den man ja mit $e->getTrace() erhält, beginnt dummerweise mit der Zeile, in der die Exception geworfen wurde, und nicht mit der ders eigentliche Fehlers. Deshalb kann man dummerweise nicht den gleichen Top-Level-Exception-Handler verwenden, den man für Standard-Exceptions verwendet.
Eher unbekannt ist, dass es in PHP schon eine erweitere Exception namens ErrorException gibt. Mit folgendem simplen Code ist es problemlos möglich, Fehler auf Exceptions zu mappen:
function exception_error_handler($errno, $errstr, $errfile, $errline ) {
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
}
set_error_handler("exception_error_handler");
Wer das ganze jetzt ein wenig aufmöbeln will, kann in diesem error handler jetzt noch den error_reporting Level abfragen, damit nur Fehler Exceptions werfen, die das auch wirklich sollen.
ErrorException im PHP Manual
Exceptions im PHP Manual
error_reporting() im PHP Manual
Freitag, 10. Oktober 2008
Speicherverbrauch und große Arrays in PHP
Heute hatte ich das Problem, dass ich einem Kunden etwa 10.000 Datensätze aus einer Online-MySQL-Datenbank als Download zur Verfügung stellen sollte. Dieser Download sollte alle Datensätze live ausgeben, natürlich als Excel-Datei.
Wie immer habe ich dafür unser firmeneigenes MVC-Framework verwendet. Im Controller alle Datensätze geholt, in ein Array geschrieben und ans View übergeben, damit dieses daraus eine HTML-Tabelle generiert, welche bekanntlich problemlos in Excel importiert werden kann.
Allerdings schmierte PHP regelmäßig bei etwa 8.000 Datensätzen ab: Speicher voll. 40MB standen auf dem Hosting-Paket pro Script zur Verfügung, 50MB wollte PHP belegen.
Schritt 1:
Überall den Speicherverbrauch mit memory_get_usage() gemessen. Klar, das Array aus der Datenbank ist groß, etwa 18 MB. Aber diese Daten brauche ich nun mal. Also in der View-Komponente geschaut, was sich da machen lässt.
Die View-Komponente erstellt erst die HTML-Tabelle und baut dann am Ende einfach die html-Tags für eine gültige Seite drumherum, also
$output = ''.$content.'';
Aha, in dieser Zeile schmiert PHP ab. Begründung:
Bug #44069 Huge memory usage with concatenation using . instead of .=
Also umgebaut auf:
$output = '';
$output .= $content;
$output .= '';
Speicherverbrauch kleiner, aber Problem noch nicht gelöst.
Schritt 2:
Das Framework verwendet standardmäßig gzip-Kompression, um die Daten auszuliefern. Die Kompression dieser Masse an Daten kostet Speicher. Also die Kompression ausgeschaltet. Resultat: Speicherverbrauch kleiner.
Eine Zeit lang gehts gut. Dann werden die Kunden-Daten mehr und der Download schmiert schon wieder ab. OK, also weiter schauen.
Schritt 3:
Eine Auffälligkeit im Controller:
[sourcecode language='php']
echo memory_get_usage(); // ergibt 18 MB
// überflüssigen Key löschen, den der Kunde nicht in seiner Tabelle braucht
foreach ($data as $key=>$row) {
unset($data[$key]['id']);
}
echo memory_get_usage(); // ergibt 23 MB
[/sourcecode]
Was ist denn nu los? Ich lösche Daten aus dem Array, gebe den Speicher frei, aber der Speicherverbrauch steigt? OK, dafür habe ich immer noch keine Erklärung. Also, die MySQL-Query so umgebaut, dass ich das id-Feld gar nicht erst holen muss. Schleife gelöscht. Speicherverbrauch bleibt bei 18 MB. Schonmal besser, aber das reicht nicht.
Schritt 4:
Nach langem Suchen zu Speicherproblemen bei großen Arrays bin ich letztendlich zu der Lösung gekommen: PHP braucht einfach viel Speicher, um große Arrays zu verwalten. Bei 10.000 Datensätzen mit etwa 15 Feldern muss PHP 150.000 Variablen verwalten. Ups, sorry, PHP.
Also hab ich, nachdem ich alle Datensätze geholt habe, jeden Datensatz als String gespeichert:
[sourcecode language='php']
echo memory_get_usage(); // ergibt 18 MB
// Datensätze als String speichern
foreach ($data as $key=>$row) {
$output[$key] = implode('|', $row);
}
echo memory_get_usage(); // ergibt 2 MB
[/sourcecode]
Bestens. Hab dann ein neues View geschrieben, dass dementsprechend jeden Datensatz durchgeht, ein explode() durchführt, um die Felder wieder einzeln zu bekommen, und schon liegt mein Speicherverbrauch am Ende bei 2 MB.
Fazit:
Wie immer habe ich dafür unser firmeneigenes MVC-Framework verwendet. Im Controller alle Datensätze geholt, in ein Array geschrieben und ans View übergeben, damit dieses daraus eine HTML-Tabelle generiert, welche bekanntlich problemlos in Excel importiert werden kann.
Allerdings schmierte PHP regelmäßig bei etwa 8.000 Datensätzen ab: Speicher voll. 40MB standen auf dem Hosting-Paket pro Script zur Verfügung, 50MB wollte PHP belegen.
Schritt 1:
Überall den Speicherverbrauch mit memory_get_usage() gemessen. Klar, das Array aus der Datenbank ist groß, etwa 18 MB. Aber diese Daten brauche ich nun mal. Also in der View-Komponente geschaut, was sich da machen lässt.
Die View-Komponente erstellt erst die HTML-Tabelle und baut dann am Ende einfach die html-Tags für eine gültige Seite drumherum, also
$output = ''.$content.'';
Aha, in dieser Zeile schmiert PHP ab. Begründung:
Bug #44069 Huge memory usage with concatenation using . instead of .=
Also umgebaut auf:
$output = '';
$output .= $content;
$output .= '';
Speicherverbrauch kleiner, aber Problem noch nicht gelöst.
Schritt 2:
Das Framework verwendet standardmäßig gzip-Kompression, um die Daten auszuliefern. Die Kompression dieser Masse an Daten kostet Speicher. Also die Kompression ausgeschaltet. Resultat: Speicherverbrauch kleiner.
Eine Zeit lang gehts gut. Dann werden die Kunden-Daten mehr und der Download schmiert schon wieder ab. OK, also weiter schauen.
Schritt 3:
Eine Auffälligkeit im Controller:
[sourcecode language='php']
echo memory_get_usage(); // ergibt 18 MB
// überflüssigen Key löschen, den der Kunde nicht in seiner Tabelle braucht
foreach ($data as $key=>$row) {
unset($data[$key]['id']);
}
echo memory_get_usage(); // ergibt 23 MB
[/sourcecode]
Was ist denn nu los? Ich lösche Daten aus dem Array, gebe den Speicher frei, aber der Speicherverbrauch steigt? OK, dafür habe ich immer noch keine Erklärung. Also, die MySQL-Query so umgebaut, dass ich das id-Feld gar nicht erst holen muss. Schleife gelöscht. Speicherverbrauch bleibt bei 18 MB. Schonmal besser, aber das reicht nicht.
Schritt 4:
Nach langem Suchen zu Speicherproblemen bei großen Arrays bin ich letztendlich zu der Lösung gekommen: PHP braucht einfach viel Speicher, um große Arrays zu verwalten. Bei 10.000 Datensätzen mit etwa 15 Feldern muss PHP 150.000 Variablen verwalten. Ups, sorry, PHP.
Also hab ich, nachdem ich alle Datensätze geholt habe, jeden Datensatz als String gespeichert:
[sourcecode language='php']
echo memory_get_usage(); // ergibt 18 MB
// Datensätze als String speichern
foreach ($data as $key=>$row) {
$output[$key] = implode('|', $row);
}
echo memory_get_usage(); // ergibt 2 MB
[/sourcecode]
Bestens. Hab dann ein neues View geschrieben, dass dementsprechend jeden Datensatz durchgeht, ein explode() durchführt, um die Felder wieder einzeln zu bekommen, und schon liegt mein Speicherverbrauch am Ende bei 2 MB.
Fazit:
- Aufpassen bei sehr großen Arrays. Der Speicherverbrauch steigt enorm an.
- Das MVC-Pattern hat eine Schwachstelle. Man kann nicht sauber die Ergebnisse einer MySQL-Query streamen (damit hätte ich das Problem ja auch nicht gehabt), weil ja immer der Controller seine Daten an das View übergeben muss. Das wird irgendwann immer zu einem Speicherproblem führen.
Für Lösungen oder Hinweise dazu bin ich immer dankbar.
Montag, 15. September 2008
htaccess lokal verändern
Ab und zu läuft man doch gern mal in das Problem, dass die lokale htaccess für ein Projekt etwas anders lauten muss als die Variante für den Staging- oder Live-Server. Denn schließlich ist nicht jeder Apache gleich konfiguriert. Glücklicherweise gibt es in der httpd.conf eine schöne Lösung, dieses Problem zu umgehen.
Einfach in der Apache-Konfiguration httpd.conf folgende Zeile suchen:
AccessFileName .htaccess
und durch diese ersetzen:
AccessFileName .htaccess_local .htaccess
Sollte man jetzt also für lokale Zwecke eine angepasste .htaccess brauchen, braucht man sich jetzt einfach nur eine .htaccess_local anzulegen, und schon hat diese Vorrang vor der eigentlichen .htaccess.
Aber aufpassen: Es wird immer nur der beiden Dateien ausgeführt. Es ist also nicht so, dass die zweite z.B. die erste ergänzt.
Und wieder ein Problem weniger.
Einfach in der Apache-Konfiguration httpd.conf folgende Zeile suchen:
AccessFileName .htaccess
und durch diese ersetzen:
AccessFileName .htaccess_local .htaccess
Sollte man jetzt also für lokale Zwecke eine angepasste .htaccess brauchen, braucht man sich jetzt einfach nur eine .htaccess_local anzulegen, und schon hat diese Vorrang vor der eigentlichen .htaccess.
Aber aufpassen: Es wird immer nur der beiden Dateien ausgeführt. Es ist also nicht so, dass die zweite z.B. die erste ergänzt.
Und wieder ein Problem weniger.
Mittwoch, 3. September 2008
Texte vergleichen leicht gemacht
Vor einigen Tagen ist endlich mein kleines "Nebenbei"-Projekt "texte-vergleichen.de" online gegangen. Und das soll nur der Anfang sein. Aber worum geht es? Ganz einfach. Um ein Tool, welches einem hilft, verschiedene Versionen von Texten und Dokumenten zu vergleichen.
Viele (gerade die Programmierer unter euch) werden schon die typischen Verdächtigen wie WinMerge, KDiff3, Kompare usw. kennen. Alles super Tools, doch haben sie alle dasselbe Problem: Sie sind Desktop-Programme, die installiert werden müssen. Bei texte-vergleichen.de kann man dieses jetzt endlich online machen, ohne irgendetwas erst installieren zu müssen. Und das alles schön hübsch grafisch aufbereitet. Sehr praktisch also, wenn man mal unterwegs ist und irgendwas vergleichen muss.
Der Hauptfokus der Anwender wird neben Programmierern aber auf Anwendern liegen, die eher mit journalistisch aufbereitetem Text zu tun haben. Daher gibt es z.B. Funktionen, um sich doppelte Leerzeichen anzeigen zu lassen oder später auch eine Rechtschreibprüfung.
Bin natürlich immer für Ideen und Korrekturen offen.
Viele (gerade die Programmierer unter euch) werden schon die typischen Verdächtigen wie WinMerge, KDiff3, Kompare usw. kennen. Alles super Tools, doch haben sie alle dasselbe Problem: Sie sind Desktop-Programme, die installiert werden müssen. Bei texte-vergleichen.de kann man dieses jetzt endlich online machen, ohne irgendetwas erst installieren zu müssen. Und das alles schön hübsch grafisch aufbereitet. Sehr praktisch also, wenn man mal unterwegs ist und irgendwas vergleichen muss.
Der Hauptfokus der Anwender wird neben Programmierern aber auf Anwendern liegen, die eher mit journalistisch aufbereitetem Text zu tun haben. Daher gibt es z.B. Funktionen, um sich doppelte Leerzeichen anzeigen zu lassen oder später auch eine Rechtschreibprüfung.
Bin natürlich immer für Ideen und Korrekturen offen.
Abonnieren
Posts (Atom)