PHPGangsta - Der praktische PHP Blog

PHP Blog von PHPGangsta


Archive for Oktober, 2009

Asynchron Aufgaben erledigen (lassen)

with 9 comments

Was könnte der Autor damit meinen, werdet ihr euch fragen. Naja, das ist schnell erklärt: hier und da hat man mal kleine Aufgaben, die evtl. etwas länger dauern, von dessen Ergebnis der User aber nicht wissen muss. Warum also soll man damit seine PHP-Scripte ausbremsen, und den User warten lassen? Ihn interessiert das Ergebnis ja (erstmal) nicht.

Beispiel: Nehmen wir an, das Versenden eine Informations-Email würde 1 Sekunde dauern (wenn man über einen externen SMTP versendet und nicht über den lokalen, kann das sogar sein). Dann bemerkt der User auf der Webseite eine Verzögerung, die Seite wird langsam.

Es gibt sicherlich noch bessere Beispiele, wie z.B. das Berechnen eines Vorschaubildes. Der User lädt ein Bild hoch (Avatar), und wir brauchen davon eine verkleinerte Version. Wenn dieser Algorithmus nun langsam wäre (was er im Normalfall nicht ist), würde der User vor seinem Bildschirm Däumchen drehen.

Für all solche Aufgaben (ihr findet sicherlich bessere) ist es sinnvoller, diese Aufgaben asynchron erledigen zu lassen. Da gibt es mindestens 2 Möglichkeiten:

  • Man trägt die Aufgabe in eine Task-Tabelle in der Datenbank ein, und lässt ein weiteres Script (Cronjob?) diese Task-Liste abarbeiten.
  • Man lässt einen Kindprozess diese Arbeit erledigen, welcher geforkt wird, und non-blocking ist

In beiden Fällen bekommt der User direkt die nächste Seite zu sehen und kann weiterarbeiten, der asynchrone Prozess kann aber erst einige Sekunden/Minuten später fertig sein.

Wie setzt man das nun um? Ich glaube, die erste Möglichkeit sollte jedem von euch bekannt sein, da es keine Schwierigkeit ist, eine neue Zeile in einer Datenbank-Tabelle einzufügen, und auch ein Script, das periodisch diese Zeilen liest und abarbeitet, sollte kein Hexenwerk sein.

Die zweite Möglichkeit haben wahrscheinlich deutlich weniger Leute genutzt. Ich finde sie auch ehrlich gesagt unschöner, aber möchte sie trotzdem vorstellen (da sie schneller umzusetzen ist, und häufig auch zum Ziel führt). Als erstes fällt einem da vielleicht der system() Aufruf ein, mit dem man einen weiteren Prozess starten kann. Das Problem daran ist, dass er blocking ist, sprich der nächste Befehl in PHP erst ausgeführt wird nachdem der System-Aufruf beendet ist. Genau das wollen wir ja nicht. Aber unter jedem Betriebssystem gibt es da Möglichkeiten, den Prozess in den Hintergrund zu schieben. Der system() Aufruf ist dann sofort beendet und PHP läuft weiter, parallel läuft ein weiterer Prozess mit der Aufgabe.

function execInBackground($cmd) {
	if (substr(php_uname(), 0, 7) == "Windows") {
		pclose(popen("start /B ". $cmd, "r"));
	}
	else {
		system($cmd . " > /dev/null &");
	}
 }

In Maugrim The Reaper’s Blog gibt es eine sehr ausführliche Serie zu dem Thema, mit vielen weiteren Beispielen und Code. Auf jeden Fall lesenswert.

Eine andere interessante Möglichkeit ist Gearman.
http://toys.lerdorf.com/archives/51-Playing-with-Gearman.html

Wenn man etwas andere Voraussetzung hat, könnten die curl_multi_* Funktionen interessant sein. Diese kann man nutzen, wenn man viele Dinge zu erledigen hat, aber auf die Ergebnisse warten muß (weil man sie dem User anzeigen muss). Man möchte also nicht asynchron, sondern simultan arbeiten. Das ganze ist in einem Blogartikel zum Thema „Simultaneuos HTTP requests in PHP with cURL super beschrieben.

Written by Michael Kliewe

Oktober 30th, 2009 at 7:49 pm

Posted in PHP

Tagged with , , , , ,

Javascript Web Worker

without comments

Ergänzend zu einem älteren Artikel über Google Gears hier eine kurze Statusmeldung: Mittlerweile sind die modernen Browser (zur Zeit Firefox 3.5, Safari 4 und Google Chrome) in der Lage, die Funktionalität der Worker auch ohne das Gears-Plugin anzubieten, sodass man auf eine große Anzahl an Nutzern zurückgreifen kann. Wir werden also in Zukunft vermehrt tolle Seiten mit vielen Effekten und Funktionalitäten sehen, und vielleicht ja auch selbst entwickeln.

Weitere Infos bietet die Suchmaschine eurer Wahl. Einfach nach „Javascript Web Worker“ suchen, es gibt bereits einige Beispiele und auch den Draft, der diese Technik hoffentlich bald zum Standard macht.

Written by Michael Kliewe

Oktober 28th, 2009 at 9:24 pm

Posted in PHP

Tagged with , ,

Konstanten, Klassenkonstanten und der Zugriff

with 2 comments

Variablen kennt jeder. Konstante werden deutlich seltener genutzt, haben aber auch ihre Daseinsberechtigung, vor allem bei Werten, die wir einmalig am Anfang setzen wollen und im späteren Verlauf auf keinen Fall geändert werden sollen.

Normale Konstanten in PHP werden angelegt mit

define('SERVICE_PORT', 312);

Darauf zugegriffen wird einfach mit dem Konstantennamen:

echo SERVICE_PORT;

In der objektorientierten Welt grauselt es einem, wenn man Konstanten einfach so in der Welt rumfliegen hat. Konstanten gehören meistens zu einer Klasse, und sollten demnach nicht im globalen Namensraum definiert werden.

Das kann man wie folgt machen:

class My_User
{
	const STATUS_DELETED = -1;
	const STATUS_REGISTERED = 0;
	const STATUS_ENABLED = 1;

	private $_status;

	public function __construct() {
		$this->_status = self::STATUS_ENABLED;	
	}
}

Innerhalb der Klasse kann man also mit self und dem zweifachen Doppelpunkt darauf zugreifen. Von außerhalb der Klasse funktioniert das genauso:

echo My_User::STATUS_DELETED;

Etwas trickreicher ist es, wenn man den Konstantennamen in einer Variablen gespeichert hat und dann darauf zugreifen möchte. Hier mein erster Versuch, der natürlich nicht funktioniert hat:

$status = 'STATUS_REGISTERED';
echo My_User::$status;

In diesem Fall versuchen wir nicht, die Klassenkostante STATUS_REGISTERED auszugeben, sondern die statische Klassenvariable $status. Die Fehlermeldung lautet aussagekräftig:

Fatal error: Access to undeclared static property: My_User::$status in constant.php on line 17

Auch andere Versuche wie z.B.

echo My_User::'STATUS_REGISTERED';
echo My_User::{'STATUS_REGISTERED'};

scheitern.
Des Rätsels Lösung ist die PHP-Funktion constant(). Damit kann man nicht nur normale Konstanten abrufen, sondern auch Klassenkonstanten:

echo constant('SERVICE_PORT');
echo constant('My_User::' . $status);

———–

Es gibt übrigens noch eine weitere Methode, die man aber tunlichst NICHT verwenden sollte:

eval('echo My_User::' . $status . ';');

Written by Michael Kliewe

Oktober 28th, 2009 at 8:28 am

Posted in PHP

Tagged with , , ,

PHP beschleunigen mit dem APC

with 5 comments

Frameworks im Allgemeinen sind eine sehr tolle Sache. Sie bieten eine Menge Klassen und Funktionalitäten, um einem die Arbeit zu erleichtern. Viele Teile eines Frameworks kann man auch einzeln nutzen, und diese sind häufig auch recht schlank gehalten. Kernkomponenten jedoch, wie in diesem Beispiel das MVC-Konzept des Zend Frameworks, neigen dazu, recht viele Funktionen mitzubringen, die man evtl. garnicht alle benötigt. Das hat zur Folge, dass viele Dateien geladen werden, für die der Webserver immer auf die Festplatte zurückgreifen muss. Das selbe gilt natürlich auch für große Projekte, die kein Framework einsetzen, aber sehr viele oder große Scripte verwenden und gut besucht sind. Und wir alle wissen, dass Festplatten noch immer (auch SSDs) eine der langsamsten Komponenten in einem Rechner sind.

Aber es gibt Abhilfe: Caching! Als erstes macht es natürlich viel Sinn, im PHP-Code selbst Daten und Objekte zu cachen. Immer wiederkehrende, sich selten ändernde Datenbank-Ergebnisse zum Beispiel. Diese kann man entweder in der Session speichern, oder in memcached-Instanzen, oder oder. Über Zend_Cache und die verschiedenen Backends hatte ich ja auch bereits geschrieben.

Zweitens macht es aber auch sehr viel Sinn, die PHP-Scripte selbst zu cachen. Erstens liegen sie auf der langsamen Festplatte, und zweitens liegen sie dort ja als Textdateien. Wenn PHP aufgefordert wird, sie auszuführen, erstellt es erstmal aus dieser Textdatei einen Bytecode. Dieser Bytecode ist maschinennah, und ist dann ausführbar. Natürlich kostet dieses Übersetzen Zeit. Im Normalfall wird bei jedem Aufruf das Script neu übersetzt.

Bytecode-Caches, wie APC (Advanced PHP Cache) einer ist, speichern diesen Bytecode zwischen, nämlich im schnellen Arbeitsspeicher. Statt also bei jedem Request 50 Zend-Framework-Klassen zu übersetzen, werden sie einfach aus dem Bytecode-Cache genommen, das bringt Speed!

Der APC soll in (ferner?) Zukunft direkt mit PHP ausgeliefert werden. APC ist aktuell eine PHP-Extension, die man einfach herunterlädt, in der php.ini aktiviert, und dann bekommt man schnellere Webseiten, man muss nicht eine Zeile PHP-Code ändern. Was kann es schöneres geben?

Hier die Installation unter Ubuntu:

sudo apt-get install php-apc

fertig! Wenn man nun eine phpinfo Seite aufruft, ist dort ein Abschnitt über APC enthalten.

apc1

Man kann diese Parameter natürlich alle ändern, vor allem die Cache-Größe sollte man anpassen, damit auch alles reinpasst. Es gibt außerdem ein kleines apc-Admin-Script, mit dem man den aktuellen Stand des Caches anzeigen lassen kann (das ist im Fall von Ubuntu unter /usr/share/doc/php-apc/apc.php.gz zu finden, oder aber im Quell-Archiv der PECL). Dieses php-Script einfach an ein sicheres Verzeichnis des Webservers kopieren, und man erhält beim Betrachten:

apc2

Um die Illusion zu zerstören: Man kann die Ausführungszeit mit dem APC nicht um 99% reduzieren. Aber die 2-5 fache Performance kann man durchaus rausholen, und das mit nur 5 Minuten Arbeit!

Übrigens kann man APC nicht nur zum Cachen des Bytecodes verwenden, nach der Installation kann man auch Objekte, und Daten etc innerhalb der PHP-Scripte im Arbeitsspeicher cachen. Dazu gibt es Funktionen wie apc_store(), apc_fetch usw. Mehr dazu im PHP-Manual.

Neben APC gibt es natürlich auch noch andere Bytecode-Caches. Darunter sind XCache, eAccelerator und Zend Platform/Zend Server.

Written by Michael Kliewe

Oktober 19th, 2009 at 8:38 am

Posted in PHP

Tagged with , , ,

PHP und die lose Typisierung

with 6 comments

Heute mal eine sehr praktische Erfahrung:

Der Kollege nebenan fragte, ob ich mal kurz rübergucken kann, er verstehe die Ausgabe einer Funktion nicht. Vereinfacht sah der Code so aus (über Sinn und Unsinn soll hier nicht diskutiert werden, es ist nur vereinfacht dargestellt):

function show(array $headerArray) {
	foreach ($headerArray as $headerKey => $headerValue) {
		$outputString = $headerKey . ':';
		foreach ($headerValue as $valueKey => $value) {
			if ($valueKey != 'append') {
				$outputString .= $value;
			}
		}
		return $outputString;
	}
}

Die Funktion erwartet ein Array mit Header-Informationen. Diese sollen durchlaufen werden, und die Inhalte sollen als Wertepaare-String zurückgegeben werden, wenn der innere Key nicht „append“ ist.

Soweit ist der Code OK. Jetzt kommt der Aufruf:

$headerArray = array("From" => array(
						0 		=> 'john.doe@some.city',
						'append'=> true			));

echo show($headerArray);

Wie lautet die Ausgabe? Korrekt, nämlich so:

From:

Wenn ich den Aufruf leicht ändere:

$headerArray = array("From" => array(
						'0' 		=> 'john.doe@some.city',
						'append'=> true			));

echo show($headerArray);

Wir nutzen also als Key nun den String ‚0‘. Jetzt sollte das richtige rauskommen. Aber man liegt immernoch falsch:

From:

Da wurde ich stutzig. Man sollte denken, jetzt wird der String ‚0‘ mit dem String ‚append‘ verglichen. Da es ungleich ist, sollte die Email-Adresse ausgegeben werden. Falsch gedacht.

PHP scheint den Key eines Arrays als Integer zu casten wenn möglich. Egal ob wir 0 oder ‚0‘ als Key verwenden, beim Auslesen des Arrays erhalten wir immer einen Integerwert. Das sollte man sich auf der Zunge zergehen lassen.
Als Folge vergleicht er also immer den Integer 0 mit dem String ‚append‘. Damit ein Vergleich möglich ist, castet PHP den String korrekterweise als Integer, und das ergibt ja bekannterweise 0.

Das ursprüngliche Problem lag natürlich beim != . Man hätte das ganze vermeiden können, indem man !== verwendet, dann wäre das Problem nicht aufgetreten, und ich hätte einen Artikel weniger.

Written by Michael Kliewe

Oktober 12th, 2009 at 12:02 pm

Posted in PHP

Tagged with , ,