PHPGangsta - Der praktische PHP Blog

PHP Blog von PHPGangsta


Archive for the ‘PHP’ Category

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 , ,

Client-IP Problem bei Reverse-Proxy-Betrieb

without comments

In einem meiner letzten Artikel schrieb ich ja bereits über Reverse-Proxies. Der Reverse-Proxy nimmt die Verbindung vom Client (Browser) entgegen, dann kann er entweder selbst den Request bedienen (statische Dateien von der lokalen Platte oder aus dem Cache), oder er verbindet sich zu einem der Backend-Webserver, ruft dort die geforderte Datei ab, und sendet sie dem Client zurück.

Ein Problem entsteht nun auf dem Backend-Webserver: Alle Requests kommen vom Reverse-Proxy. Wenn nun in den PHP-Scripten die Client-IP-Adresse verwendet wird, steht darin die IP des Reverse-Proxies.

Betroffen ist in diesem Fall die PHP-Variable  $_SERVER[‚REMOTE_ADDR‘]  als auch das Apache-Log, denn dort taucht auch immer nur die IP des Reverse-Proxy auf.

xforwardedfor3

127.0.0.1 - - [03/Oct/2009:10:45:24 +0200] "GET /phpinfo.php HTTP/1.0" 200 7800 "-" "Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 (.NET CLR 3.5.30729) FirePHP/0.3"

127.0.0.1 deshalb, da ich direkt auf der Linux-Maschine sowohl den nginx als auch den Apache laufen habe.

Da gibt es natürlich Lösungen. Zuerst einmal müssen wir die Client-IP irgendwie an den Backend-Webserver übergeben. Dafür gibt es den Header „X_FORWARDED_FOR“, da wird der nginx die Client-IP reinschreiben.

Im nginx muss dann folgendes gesetzt werden:

location / {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_redirect off;
........

Ein phpinfo() liefert dann die korrekte Client-IP in X_FORWARDED_FOR (34 ist der Server, 33 der Client):

xforwardedfor4

Nun installieren wir noch ein Apache-Modul. Dieses Modul sorgt dafür, dass in die Variable $_SERVER[‚REMOTE_ADDR‘]  der Wert aus X-FORWARDED-FOR geschreiben wird, damit wir keine PHP-Scripte anpassen müssen. Außerdem sorgt dieses Modul dafür, dass im Apache-Log dieser Wert auftaucht.

Das Module, das es für diese Aufgabe gibt, lautet „mod_rpaf“. Einfach danach googlen, downloaden und in der Apache-Konfiguration laden. Oder unter Linux:

sudo apt-get install libapache2-mod-rpaf

Noch kurz konfigurieren /etc/apache2/mods-available/rpaf.conf:

<IfModule mod_rpaf.c>
RPAFenable On
RPAFsethostname On
RPAFproxy_ips 127.0.0.1
</IfModule>

Das Ergebnis sieht dann so aus:

xforwardedfor2

192.168.1.33 - - [03/Oct/2009:10:47:23 +0200] "GET /phpinfo.php HTTP/1.0" 200 7808 "-" "Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 (.NET CLR 3.5.30729) FirePHP/0.3"

Möchte man das Modul nicht installieren, muß man überall in seinen PHP-Scripten die Variable $_SERVER[‚X_FORWARDED_FOR‘] statt $_SERVER[‚REMOTE_ADDR‘] nutzen, und das Apache-Log anpassen:

LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined

Damit hätten wir das Problem auch gelöst, überall steht nun die Client-IP zur Verfügung, die Anwendungen und Logs laufen wieder korrekt.

Written by Michael Kliewe

Oktober 7th, 2009 at 8:35 am