Posts Tagged ‘PHP’

Mittwoch, 22. Mai 2013

Konkurrenz-Probleme in PHP

In den letzten Wochen habe ich intensiv an meinem Raspberry Pi-Dashboard für unser Apartment geschraubt und gebastelt. Verschiedene Tiles werden mittels AJAX-Requests, welche über jQuery abgesetzt werden, mit Inhalten versorgt — und dies regelmässig alle paar Minuten.

Ein mysteriöser Bug machte mir in den letzten Tagen das Leben schwer: Es konnte vorkommen, dass zwei Tiles mit den Aktienkursen desselben Unternehmens gefüllt wurden, obwohl die Tile-IDs unterschiedliche ISINs trug.

Nach einigen Minuten Debugging schwante mir dann plötzlich, was das Problem war: Der auf dem Server lokal zwischengespeicherte HTML-Cache war zwar in Dateien mit der richtigen ISIN im Dateinamen abgelegt — der Inhalt war aber identisch. Konkret enthielt die HTML-Datei über das Unternehmen AAPL die Daten für das Unternehmen GOOG.

Wie sich heraustellte, hatte ich mein Script nicht dermassen intelligent programmiert, dass es mit mehreren, nur wenige Millisekunden auseinanderliegenden Requests ordnungsgemäss umging. Um nämlich den Aktienkurs einer ISIN abzufragen, muss dieser zuerst über einen HTTP-Request an ein Suchscript meines unfreiwilligen Kursanbieters in eine vom Anbieter intern verwendete NOTATION_ID umgewandelt werden (was wissbegierigen Lesern dieses Blogs einen Hinweis gibt, von welcher Web-Site ich die Kursdaten beziehe). Erst danach kann ich die Informationsseite des auf dem Aktienmarkt gehandelten Unternehmens aufrufen, herunterladen und im lokalen Dateisystem ablegen.

Konkret war mein Fehler, dass ich die Suchergebnisse zur Umwandlung der ISIN in die NOTATION_ID in eine Cache-Datei mit statischem Dateinamen ablegte. Da die Requests mit wenigen Millisekunden Versatz beim Server ankamen, war die Cache-Datei manchmal bereits von einem späteren Request überschrieben worden, als ich den HTML-Code endlich in PHP einlesen wollte (ich denke, dass wir hier von einigen wenigen hundert Millisekunden sprechen). Dies führte dazu, dass ich einer ISIN fälschlicherweise die NOTATION_ID des nachfolgenden Requests zuwies und diese plötzlich für zwei Kursanbieter galt. Da ich dieses Mapping aus Performance-Gründen ebenfalls serialisiert in einer Cache-Datei ablegte, wurden ab diesem Zeitpunkt zwei Tiles mit identischen Inhalten befüllt.

Die erste Lösung, dem Dateinamen einen sich ständig ändernden Wert einzupflegen, scheiterte grandios, weil ich time() verwendete. Da die Requests innerhalb der selben Sekunde auf dem Server eintreffen konnten, konnte die Cache-Datei im schlimmsten Fall weiterhin vom nachfolgenden Request überschrieben werden. Die zweite Lösung war deshalb, durch das Einfügen einer mittels rand(1000,9999) definierten Zufallszahl einen wahrlich zufälligen Wert in den Dateinamen einzuschleusen. Die Chancen, einen identischen Cache-Namen zu erwischen, ist hier 1:10000 — good enough, würde ich sagen.

Natürlich hätte ich auch einfach die ISIN in den Namen der Cache-Datei aufnehmen können. Hier hegte ich aber Zweifel, weil ich nicht wollte, dass bei einem Downloadfehler einfach unbemerkt die bestehenden, veralteten Daten eingelesen würden.

Tags: , , , , , , ,
Labels: Programmierung

1 Kommentar | neuen Kommentar verfassen

Sonntag, 12. Mai 2013

Wetter-Icons als Web-Font in ein Dashboard einbinden

Derzeit arbeite ich mit Hochdruck an einem Dashboard, welches ich in unserer Mietwohnung in Form eines TFT-Bildschirms beim Eingangsbereich platzieren möchte. Das Dashboard basiert softwaretechnisch auf HTML, CSS, JavaScript und PHP. Es integriert gecachte HTML-Dateien, JSON-Abfragen (MEZI, Twitter) und sogar einen iCalendar.

Ein Element (resp. “Tile”) des Dashboards wird die Wettervorhersage für den morgigen Tag sein. Um nicht nur eine qualitative Beschreibung des Wetters anzuzeigen, sondern auch ein Icon, habe ich mich natürlich vom derzeitigen (technologischen wie designtechnischen) Platzhirschen forecast.io inspirieren lassen. Dessen mit HTML5-Canvas animierten Icons waren dann aber doch Overkill für mich.

Stattdessen habe ich auf die Meteocons Web-Font von Alessio Atzeni zurückgegriffen. Die Web-Font ist in der Public Domain und lässt sich somit problemlos in ein HTML5/CSS3-Web-Projekt einbinden.

Leider sind die Icons meines Wissens nicht einem standardisierten Wettertyp zugeordnet, weshalb ich dieses Mapping mittels eines PHP-Arrays von Hand nachholen musste. Das Resultat lässt sich durchaus sehen:

eMeidi.local Dashboard Weather Forecast

Tags: , , , ,
Labels: Web

3 Kommentare | neuen Kommentar verfassen

Mittwoch, 24. April 2013

WordPress verbieten, YouTube-Links automatisch einzubetten

Um zu verhindern, dass YouTube-Links in einem WordPress-Beitrag automatisch als Objekt eingebettet werden, muss man in seinem Theme in den entsprechenden Dateien des Themes (bei mir: single.php) zuoberst im PHP-Block folgenden Befehl einfügen:

...
remove_filter( 'the_content', array( $GLOBALS['wp_embed'], 'autoembed' ), 8 );
...

Tags: , , , , ,
Labels: Web

Keine Kommentare | neuen Kommentar verfassen

Donnerstag, 14. Februar 2013

Data Mining mit Ubuntu, MySQL, PHP und Python

Beruflich setze ich mich derzeit mit der Analyse von Inhalten von Web-Sites auseinander. Nachfolgend habe ich einige Erfahrungen aufgelistet, welche ich dabei gemacht habe.

Wir verarbeiten in diesem Projekt Web-Sites, welche wir mit entsprechenden Tools aus dem Web auf den lokalen Rechner gespiegelt haben. Die Web-Site Assets liegen im Dateisystem. Zur Weiterverarbeitung der Daten wurden die HTML-, JS-, CSS- und XML-Rohdaten in eine MySQL-Tabelle gespitzt (12GB) und anschliessend mit Meta-Daten ergänzt.

Ich persönlich bin nicht sicher, ob die Ablage von HTML-Code in der Datenbank die sinnvollste und performanteste Lösung ist, aber dies war nunmal der Stand des Projektes als ich dazu gestossen bin — und daran liess sich nichts mehr rütteln.

Da wir unter anderem auch Volltextsuchen auf die Grunddaten anwenden, hätte ich mir Apache Solr genauer angeschaut und darauf mittels PHP und JSON zugegriffen.

Kommandozeile

Da der Ubuntu-Server aus mir unerfindlichen Gründen mit LAMPP aufgesetzt wurde, befinden sich die Binaries wie php nicht in den Standardpfaden und werden von bash ohne absolute Pfadangabe nicht gefunden. Da man den Interpreter täglich dutzende, wenn nicht gar hunderte Male aufruft, ist es ratsam, das Verzeichnis sofort in die $PATH-Variable des Shells aufzunehmen.

Hier findet man sich in der Shell Startup File Hell wieder. Je nachdem, ob man lokal arbeitet oder sich per SSH einloggt muss der Befehl an einem anderen Ort stehen. Schlussendlich habe ich die nachfolgende Zeile …

...
PATH="$PATH:/opt/lampp/bin"

… ans Ende folgender zwei Dateien angefügt:

  • ~/.profile
  • ~/.bashrc

Datenbank (MySQL)

Cache aktivieren

Wir führen PHP-Scripts über die Linux-Shell aus. Gerade bei der Entwicklung neuer Scripts sind verschiedene Anläufe nötig, bis alle Bugs und nicht beabsichtigten Funktionen ausgemerzt sind. Da wir oftmals an einem Datenset von 60’000+ Seiten arbeiten, ist es unabdingbar, dass wir eine Cacheing-Lösung anwenden, um Datenbankabfragen im Kurzspeicher zwischenzulagern.

Folgende Parameter in my.cnf aktivieren den in MySQL vorhandenen Cache:

...
[mysqld]
...
query_cache_type = 1
query_cache_size = 512M
query_cache_limit = 32M

Der Geschwindigkeitsgewinn ist immens — nachdem ein Script mit SELECT-Statements zum ersten Mal ausgeführt wurde und dafür mehrere Minuten benötigte, rauscht es in den folgenden Malen innert Sekunden durch.

Programmierung (PHP und Python)

Helper-Funktionen und -Klassen

Wie aus der Web-Entwicklung gewohnt sollte man bei jedem der mit der Zeit entstehenden Scripts zu Beginn Klassen, allgemeine Einstellungen und Funktionen einbinden.

In der Funktionsbibliothek setze ich beispielsweise folgende wichtigen Parameter in globaler Form:

...
error_reporting(E_ALL);
date_default_timezone_set('Europe/Zurich');

Auch die Datenbankverbindung wird mittels meiner MySQL-Klasse hier erstellt und an alle Scripts weitergegeben, welche die Library einbinden.

Aufbau der Scripts

Als ich zum Projekt stiess herrschten Spaghetti-Code in teils monolithischen Scripts vor. Ich habe meine eigenen Scripts dann aber so entwickelt, dass sie dem Unix-Gedanken folgend normalerweise nur eine bestimmte Funktion ausführen, diese dafür aber ausgezeichnet und in sich abgeschlossen. So steht im Normalfall in jedem Script, welches Daten manipuliert, zuoberst ein SQL-Query, welches die zu verändernden Datenbankdaten auswählt.

Bei der Entwicklung wählt man hierbei ein Query, das einen oder nur wenige Werte aus der Datenbank ausliest und verarbeitet — in dieser Phase hat man keine Zeit, möglicherweise fehlerhafte Manipulationen an 60’000+ Seiten durchzuführen.

HTML mit regulären Ausdrücken parsen?

Nein, besonders nicht dann, wenn man konkret an Eigenschaften des SGML-Markup interessiert ist (bspw. Wohin zeigen Links?). Hierzu verwendet man die in PHP standardmässig enthaltene DOMDocument-Bibliothek.

Wichtig ist, dass man bei der Verwendung von E_ALL die loadHTML()-Funktion mittels @ stummschaltet, weil sonst das Terminal mit Warnungen über fehlerhaften HTML-Code (leider an der Tagesordnung) vollgespamt wird:

...
$dom = new DOMDocument();
@$dom->loadHTML($html);
$elements = $dom->getElementsByTagName('a');

HTML manipulieren — und Fallstricke

Für jede HTML-Seite erstellen wir eine Nur-Text-Version. Hierzu verwenden wir html2text.py von Aaron Swartz selig. Bevor das HTML aber umgewandelt wird, säubern wir die HTML-Datei auf eigene Faust. Auch hier kommt DOMDocument zum Zug.

Wir suchen dabei zuerst einmal Elemente jeglicher Art, deren ID oder Klasse den String nav, menu und breadcrumb enthält. Die Navigation interessiert uns nämlich nicht, und noch schlimmer: Sie verfälscht teilweise die Resultate, weil in der Navigation gesuchte Begriffe vorkommen.

Hierzu lade ich den HTML-Code wieder in eine DOMDocument und iteriere danach über alle Elemente auf der Suche nach den besagten IDs und Klassennamen:

function cleanNavMenuElements($html = null) {
		$dom = new DOMDocument();
		@$dom->loadHTML($html);
		
		$changesMade = false;
		
		$elements = $dom->getElementsByTagName('*');
		foreach($elements as $element) {
			if(preg_match('/^(html|body)$/',$element->nodeName)) {
				// Otherwise we might delete the whole DOM!
				continue;
			}
			
	        if($element->hasAttribute('class') && preg_match('/(nav|menu|breadcrumb)/i',$element->getAttribute('class')) > 0) {
				status('Found element with class ' . $element->getAttribute('class'));
				$element->parentNode->removeChild($element);
				
				$changesMade = true;
				
				// Don't go further if we removed this node already
				continue;
			}
			
			if($element->hasAttribute('id') && preg_match('/(nav|menu|breadcrumb)/i',$element->getAttribute('id')) > 0) {
				status('Found element with id ' . $element->getAttribute('id'));
				$element->parentNode->removeChild($element);
				
				$changesMade = true;
			}
	    }
		
		if(!$changesMade) {
			return $html;
		}
		
		return $dom->saveHTML();
	}

Ein Problem manifestiert sich bei der Manipulation aber: Jegliche Anpassungen erfolgen live, was verwirrende Folgen für Schleifen haben kann.

Da die Suche nach obigen ID- und Klassennamen nicht alle Navigationselemente eliminiert, suche ich in einem zweiten Anlauf Tabellen und Listen, deren Elemente ausschliesslich Links enthalten. Dies ist ein guter Indikator, ein Navigationsblock gefunden zu haben.

Hier ist das Problem des sich bei jeder Iteration veränderndem DOM aber sehr ausgeprägt. Wenn ich deshalb durch td-Elemente und li-Elemente iteriere, verwende ich nicht foreach() sondern eine for()-Schleife, deren Counter $i ich immer dann zurücksetze, wenn ich ein Element entferne. Ansonsten wird aus Erfahrung in der Folge eines (oder mehrere Elemente) übersprungen. Damit dies klappt, arbeite ich den DOM Tabellen- respektive Listenweise ab:

$containers = $dom->getElementsByTagName($containerTag);
	
foreach($containers as $container) {
	$items = $container->getElementsByTagName($itemTag);
	
	for($i = 0; $i < $items->length; $i++) {
		$item = $items->item($i);
		
		if($item === null) {
			continue;
		}
		
		$otherTagsPresent = false;
		foreach($item->childNodes as $child) {
			$tag = $child->nodeName;

			if($tag == '#text') {
				$text = trim($child->nodeValue);
				$len = strlen($text);
				if($len < 1) {
					//#text is empty, thus not relevant
					continue;
				}
			}

			if($tag != 'a') {
				$otherTagsPresent = true;
				continue;
			}
		}

		if(!$otherTagsPresent) {
			$item->parentNode->removeChild($item);
			$i = $i-1;
		}
	}

Weiterverarbeitung der Daten durch Nicht-IT-Profis

Zur Weiterverarbeitung der Auswertungen durch andere, in IT nicht versierte Mitarbeiter habe ich eine Funktion geschrieben, welche eine Pfadangabe sowie CSV-Daten als Argumente übertragen erhält. Die Datei wird geschrieben und gleich anschliessend mittels eines kleinen Python-Scripts (Stichwort: openpyxl) in das bei uns hauptsächlich verwendete XLSX-Format konvertiert.

Tags: , , , , , , , , ,
Labels: Programmierung

Keine Kommentare | neuen Kommentar verfassen

Mittwoch, 13. Februar 2013

Mit PHPExcel eine xlsx-Datei parsen und in MySQL importieren

Die einfachste Methode, die sich nach dem Studium der Dokumentation und Stackoverflow-Posts zum Thema PHPExcel ergeben hat, greift auf die PHPExcel_Worksheet::toArray()-Funktion zurück:

$objReader = PHPExcel_IOFactory::createReader(PHPExcel_IOFactory::identify($path));
$objReader->setReadDataOnly(true);

$objPHPExcel = $objReader->load($path);

$sheets = $objPHPExcel->getSheetNames();	
$sheetsRev = array_flip($sheets);

if(!isset($sheetsRev['Queries'])) {
	die('$sheetsRev[Queries] not set');
}

$objSheet = $objPHPExcel->setActiveSheetIndex($sheetsRev['Queries']);

$data = array();
foreach($objSheet->toArray() as $rowNum=>$row) {
	foreach($row as $cellNum=>$value) {
	...
	}
}

Es gibt eine andere Lösung, welche aber erforderlich macht, dass man die Dimension der einzulesenden Daten kennt; sprich die Anzahl der Spalten und Zeilen müssen vor der Iteration ausfindig gemacht werden. Mit toArray() überspringt man diesen unnötigen Schritt.

Tags: , , , ,
Labels: Programmierung

Keine Kommentare | neuen Kommentar verfassen

Sonntag, 7. Oktober 2012

Syntax eines PHP-Scripts auf der Kommandozeile überprüfen

Nicht immer produziere ich auf dem lokalen Mac Code in einem Verzeichnis, welches über den lokalen Web-Server angesprochen werden kann. Wenn ich den Code auf den Server des Kunden kopiert habe und mir dort eine gähnende Leere entgegenblickt, ist die Vermutung in der Regel ein Syntax-Problem, welches den PHP-Parser zum Abbruch bewegt. So genau weiss man das natürlich nicht, da man als guter Entwickler display_errors auf einem Produktivsystem auf 0/off/false gesetzt hat und Fehlermeldungen nur in das error_log ablegt.

Entweder öffnet man das error_log via FTP, oder aber man lässt die Syntax auf dem Mac selber von der Kommandozeilenversion von PHP überprüfen:

php -l <Pfad zum syntaktisch defekten PHP-Script>

Dies führt entweder zur Absolution mit

No syntax errors detected in <Pfad zum syntaktisch defekten PHP-Script>

oder aber im unglücklicheren Fall zur folgenden oder ähnlichen Fehlermeldung:

Parse error: parse error in <Pfad zum syntaktisch defekten PHP-Script> on line 146
Errors parsing <Pfad zum syntaktisch defekten PHP-Script>

Tags: , , , , ,
Labels: Programmierung

Keine Kommentare | neuen Kommentar verfassen

Montag, 16. Juli 2012

PHPs serialize und unserialize-Funktion mit Unicode-Problemen

Ich habe es mir angewöhnt, regelmässig in das PHP-Error-Log auf meinem privaten Web-Server zu schauen, in welches mit E_ALL jede erdenkliche Art von Fehlern aufgezeichnet werden.

Und kürzlich häuften sich durch einen Spider verursachte Fehler in der Form:

...
PHP Notice:  unserialize(): Error at offset 1573 of 20531 bytes in /var/www/sites/domain.ch/administration/inkludierung/file.php on line 1816
...

Heute hatte ich Zeit, dem Problem auf den Grund zu gehen. Die erste Aufgabe bestand einmal daraus, den serialisierten String vor der Speicherung in die Datenbank mit dem serialisierten String nach dem Extrakt aus der Datenbank zu vergleichen. Hierzu schrieb ich die beiden Zeichenketten in Dateien im Web-Root. Wie sich mit einem Blick erkennen liess, war der serialisierte String nach dem Zurücklesen aus der Datenbank um 30 Bytes grösser.

Was war die Ursache? Natürlich vermutete ich auf Anhieb Probleme mit dem Zeichensatz und Sonderzeichen, von welchen es in der deutschen Sprache halt ziemlich viele gibt (jedenfalls aus der Optik von 127 ASCII-Zeichen). Da PHP Offsets in Bytes (und nicht Zeichen) angibt, war es hilflos, den String in einem Texteditor wie TextMate anzuschauen. Stattdessen rief ich 0xED auf, und musste danach “offset 1573″ in Hex umrechnen — mit dem erstbesten Tool, welches ich im Web fand: Hexadecimal Conversion. Der Blick auf den Array-Wert bestätigte meine Befürchtung: Es handelte sich um eine Zeichenkette, welche deutsche Umlaute enthielt.

NB: Der Offset lag aber ca. 10 Zeichen hinter dem Auftreten der Sonderzeichen, aber immer noch innerhalb des Strings, welcher die Sonderzeichen enthielt.

Nach etwas Googlen realisierte ich, dass PHPs serialize() und unserialize() nicht den besten Ruf besitzen und äusserst fehleranfällig sind — besonders, wenn es um Multibyte-Strings geht. Nachdem ich kurzfristig auf json_encode() und json_decode() wechseln wollte, das Vorhaben aber abbrechen musste, weil ich mein Mega-Array mangels nicht fortlaufender Keys teilweise in Objekte umgewandelt zurückerhielt, stiess ich auf folgenden Blog-Post:

PHP Serialize() & Unserialize() Issues

Seit ich die serialisierten Zeichenketten noch durch Base64-En- resp. Dekodierung durchlaufen lasse, bleibt mein PHP-Error-Log leer:

...
$toDb = base64_encode(serialize($sitemap));
...
$sitemap = unserialize(base64_decode($fromDb));

Einziger Nachteil: Durch Base64-Encodierung setzt der String 7 Kilobytes an Länge an. Aber mir soll’s Recht sein.

Tags: , ,
Labels: Programmierung

3 Kommentare | neuen Kommentar verfassen

Sonntag, 6. Mai 2012

PHP kriegt das mit den Nummern nicht gebacken

Da las ich vor einigen Tagen einen interessanten Beitrag über die haarsträubenden Schwächen von PHP — und erfuhr eines der genannten Problem heute am eigenen Leibe.

Eine meiner Funktionen skaliert Bilder, um sie danach aus der Mitte heraus zuzuschneiden. Vor dem Zuschnitt wird das Bild skaliert. Ziel ist es dabei, das Bild so zu skalieren, dass sowohl die Höhe und Breite des Zielformats überschritten werden, denn so lässt sich mit gutem Gewissen ein Zuschnitt vornehmen.

Mein Code sieht folgendermassen aus:

...
for($i = 0; $i<=1; $i++) {
        $factor = $dimDestination[$i]/$dimOriginal[$i];

        $dimTests[$i] = array($dimOriginal[0]*$factor, $dimOriginal[1]*$factor, $factor);
}

$factor = null;

foreach($dimTests as $key=>$dimTest) {
        if($dimTest[0] >= $dimDestination[0] && $dimTest[1] >= $dimDestination[1]) {
                $factor = $dimTest[2];
                $keySelected = $key;
        }
        else {
                $this->debug->add($dimTest[0] . '<' . $dimDestination[0] . ', ' . $dimTest[1] . '<' . $dimDestination[1]);
        }
}

if($factor === null) {
        $this->debug->add('$factor ' . $factor . ' is null');
        return false;
}

Bei einem speziell zugeschnittenen Bild wurde $factor immer auf null gesetzt und die Funktion brach mit false ab — obwohl das Bild eigentlich problemlos skaliert und zugeschnitten hätte werden sollen.

Nach einer halbstündigen Fehlersuche kam ich den Problem schlussendlich auf die Spur. Zuerst einmal schaute ich mir die in $this->debug gespeicherten Infos an:

emeidiImage->cropFromCenter()
505<505, 131.510416667<66

Was zum Teufel?! Wieso ist 505 kleiner als 505? Mit vardump() lichtete sich der Nebel über dem falschen Verhalten:

array(2) {
  [0]=>
  array(3) {
    [0]=>
    float(505)
    [1]=>
    float(131.510416667)
    [2]=>
    float(1.05208333333)
  }
  ...
}

array(2) {
  [0]=>
  int(505)
  [1]=>
  int(66)
}

Ich verglich float(505) mit int(505), und da lag wohl die Krux vergraben! Nun, dachte ich mir, wandeln wir die Dimension halt bei der Multiplikation der Ursprungsdimension mit dem Faktor in einen Integer-Wert um:

$dimTests[$i] = array((int)($dimOriginal[0]*$factor), (int)($dimOriginal[1]*$factor), $factor);

Weiterhin brach die Funktion ab. Was zum Teufel … ein erneuter Blick auf vardump() zeigte mir endlich die wirklich Ursache des Problems:

array(2) {
  [0]=>
  array(3) {
    [0]=>
    int(504)
    [1]=>
    int(131)
    [2]=>
    float(1.05208333333)
  }
  ...
}
array(2) {
  [0]=>
  int(505)
  [1]=>
  int(66)
}

Ahaaa! Irgendwie war der Fliesskommawert eben nicht ganz genau 505, sondern vielleicht nur 504.999999.

Nach folgender Anpassung unter Zuhilfenahme der Funktion ceil() lief die Funktion endlich fehlerfrei durch:

$dimTests[$i] = array(ceil($dimOriginal[0]*$factor), ceil($dimOriginal[1]*$factor), $factor);

Tjach. Heute habe ich deshalb gelernt, dass int(505) noch lange nicht float(505) ist.

Nachtrag

Das Problem tritt mit der bei cyon.ch installierten PHP-Version 5.2.17 auf, nicht aber mit der auf meinem Entwicklungsserver installierten PHP-Version 5.4.0-3 auf.

Tags: , ,
Labels: Programmierung

Keine Kommentare | neuen Kommentar verfassen

Mittwoch, 11. April 2012

XING-Bilder grösser machen

Da ich keinen Sinn darin sehe, für eine XING Premium-Mitgliedschaft Geld auszugeben, habe ich ab und zu das Problem, dass ich erraten muss, wer mein Profil so alles besucht hat.

XING zeigt mir dabei die Besucher mit einem klitzekleinen Bildchen an, worauf ich die Person partout nicht erkennen kann. Wenn ich meine Neugier befriedigen möchte, muss ich eine XING-Mitgliedschaft kaufen.

Doch zumindest etwas Licht ins Dunkel lässt sich bringen, indem man etwas mit der Bild-URL spielt. Hier ein Beispiel:


https://www.xing.com/pubimg/users/1/6/c/2450328df.16473797,2.30x40.jpg

Hmmm! Löschen wir doch mal die Pixeldimensionen nach dem Komma. Oh:


https://www.xing.com/pubimg/users/1/6/c/2450328df.16473797,2.jpg

So sieht der Mitmensch also aus, der mein Profil besucht hat …

Mein Tipp an XING: Die verschiedenen Grössen der Bilder sollten einen variablen Bestandteil haben, der von aussenstehenden weder erraten noch hergeleitet werden kann. Beispielsweise mittels md5($imageSize . $internalUserId).

Tags: , , , , , ,
Labels: Web

Keine Kommentare | neuen Kommentar verfassen

Samstag, 11. Februar 2012

Vom Nutzen einer PHP Debug-Klasse

Seit mehreren Jahren verwende ich eine Sammlung von selber entwickelten emeidi*-PHP-Klassen, um Web-Sites und -Applikationen zu entwickeln. Leider hege ich immer noch grosse Skrupel, diese Klassen via Github als Open Source Software freizugeben — einerseits weil sich im Quellcode doch der eine oder andere unschöne Bock findet, andererseits, weil die Klassen Sicherheitslücken enthalten könnten, die bei produktiven Web-Sites ausgenützt werden könnten. Hinzu kommt mein Amazon-API-Key, den ich kurzerhand in die emeidiAmazon-Klasse festkodiert habe …

Unverzichtbares Kernstück ist dabei meine emeidiDebug-Klasse, welche für die professionelle, effiziente Web-Entwicklung unabdingbar ist. Jede andere meiner Klasse instanziert beim Laden die emeidiDebug-Klasse, mit welcher ich im ganzen Programmcode nützliche Fehlermeldungen und/oder Informationen zum Programmablauf festhalte.

Auf produktiven Web-Sites gebe ich den Inhalt des Debug-Objektes natürlich nicht aus, aber auf dem Entwicklungsserver ist es eine grosse Unterstützung, wenn ich am Ende der ausgegebenen Web-Seite noch folgenden Code hinpflanze:

...
<?php print $klasse->debug->getHtml(); ?>
</body>
</html>

Im ganzen PHP-Code finden sich zur Befüllung dieses Objekts Konstrukte wie

$this->debug->add('Not dumping descriptions because length new ' . $lenNew . ' is smaller than lenght old ' . $lenOld,'error');

Teilweise gebe ich auf produktiven Web-Sites wichtige Warnmeldungen auch in das PHP Error-Log aus. Während ich das früher direkt mit error_log(); gemacht habe, benutze ich heute ebenfalls meine Debug-Klasse dafür.

Der Grund dafür ist simpel: Nicht nur möchte ich das gesamte Debugging über meine Debug-Klasse laufen lassen, nein, meine Debug-Klasse ist auch geschwätziger und hilft mir im Falle meines Testservers, das fehlerhafte Script zu lokalisieren.

Vor einigen Tagen fand ich auf meinem Testserver folgende Einträge in der php.err-Datei:

[06-Feb-2012 00:07:43 UTC] Not dumping descriptions because length new 1778 is smaller than lenght old 1780

Mittlerweile habe ich das ausgebende Script (es wird täglich per Cron-Job aufgerufen) lokalisiert und die Programmierung angepasst. Alt hiess es:

error_log('Not dumping descriptions because length new ' . $lenNew . ' is smaller than lenght old ' . $lenOld);

Neu heisst es:

$this->debug->add('Not dumping descriptions because length new ' . $lenNew . ' is smaller than lenght old ' . $lenOld,'error',true);

Der letzte der Funktion übergebene Parameter true weist meine Debug-Klasse an, einen Eintrag in die php.err-Datei zu machen. Dieser schaut folgendermassen aus:

[11-Feb-2012 16:57:16 UTC] Not dumping descriptions because length new 1778 is smaller than lenght old 1780 in /var/www/apps/weather2ics/weather2ics.class.php on line 95 for URI </bern>

Anhand dieser Informationen kann ich nicht nur die Datei und die verantwortliche Zeile auf einen Blick lokalisieren, welche den Fehler ausgibt, sondern sehe auch noch gerade die URI, mit welcher die Web-App aufgerufen wurde.

Tags: , ,
Labels: Web

1 Kommentar | neuen Kommentar verfassen