PHPGangsta - Der praktische PHP Blog

PHP Blog von PHPGangsta


Archive for the ‘zend framework’ tag

PHP 5.3.1 und Zend Framework Bug Hunt Day

without comments

Kurze Info: PHP 5.3.1 ist gerade released worden. Über 100 Bugs wurden gefixt. Auf php.net/downloads und windows.php.net/download steht alles zum Download bereit.

Außerdem läuft gerade der November Bug Hunt Day des Zend Frameworks. Nach einem Tag sind bereits 59 Issues geschlossen worden, ich bin sehr gespannt wo der Counter morgen Abend steht!

Achja, noch eine Kleinigkeit: Falls ihr memcached 1.4.3 verwendet und mittels PHP darauf zugreift, euch sei gesagt dass die delete()-Funktion des Memcache-PECL-Moduls mit der Version nicht funktioniert. Mit 1.4.2 funktioniert alles wunderbar. Also downgraden (bzw. nicht upgraden) und auf ein Update von PECL warten. Nur so nebenbei.

Written by Michael Kliewe

November 20th, 2009 at 12:00 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 beschleunigen mittels Caching: Zend_Cache

with 6 comments

Der Titel ist vielleicht nicht ganz korrekt: PHP selbst kann man durch Caching nicht direkt beschleunigen, aber PHP-Applikationen.

Sobald man mit Datenbanken und Objekten arbeitet, fällt einem schnell auf, dass man gern für vieles eigene Objekte bastelt. Häufig ist es so, dass es für fast jede Tabelle eine Klasse gibt, und jede Zeile einer Tabelle einem Objekt entspricht. Das artet recht schnell aus, sodass man sehr viele Objekte hat, die auch hier und dort mehrfach erstellt werden. Das kostet vor allem Rechenkapazität.

Hier mal einige Klassen, mit denen wir weiter unten arbeiten werden:

class App_User
{
	private $username;
	private $newsletter;
	
	public function __construct($id) {
		$db = Zend_Registry::get('Zend_Db');
		$data = $db->fetchAll('SELECT Username, Newsletter FROM User WHERE UserID='.$id);
		$this->setUsername($data['Username']);
		$this->setNewsletter($data['Newsletter']);
	}
	
	public function getUsername() {
		return $this->username;
	}
	
	public function setUsername($username) {
		$this->username = $username;
	}
	
	public function getNewsletter() {
		return $this->newsletter;
	}
	
	public function setNewsletter($newsletter) {
		$this->newsletter = $newsletter;
	}
	
	public static function getAllUsers() {
		$db = Zend_Registry::get('Zend_Db');
		$allIds = $db->fetchCol('SELECT UserID FROM User');
		
		$users = array();
		foreach ($allIds as $id) {
			$users[] = new App_User($id);
		}
		return $users;
	}
}

Wenn man nun zum Beispiel alle User der Webseite überprüfen will, ob sie den Newsletter empfangen wollen, tut man dies objektorientiert dann so:

$newsletterCounter = 0;
$allUsers = App_User::getAllUsers();
foreach ($allUsers as $user) {
	if ($user->getNewsletter()) {
		$newsletterCounter++;
	}
}

An anderer Stelle irgendwo anders im Code (möglicherweise tief in anderen Klassen versteckt) möchte man dann vielleicht noch alle User durchgehen und ihre Usernamen ausgeben:

$allUsers = App_User::getAllUsers();
foreach ($allUsers as $user) {
	echo $user->getUsername().'<br>';
}

Nehmen wir weiter an, wir haben 5000 User in unserer Datenbank. Was passiert nun? Richtig, es werden in beiden Fällen jeweils 5000 User-Objekte erzeugt, ein Attribut abgefragt, und dann braucht man sie nicht mehr. 10000 Datenbankabfragen + 10000 Objektinstanziierungen.

Was können wir dagegen tun? Es gibt mehrere Möglichkeiten. Wir können zum Beispiel nach dem ersten Aufruf der getAllUsers()-Funktion das Ergebnis in einer globalen Variablen speichern:

$allUsers = App_User::getAllUsers();
$GLOBALS['allUsers'] = $allUsers;

Das ist vergleichbar mit der Zend_Registry, es funktioniert intern ähnlich, ist aber weit schöner und ein Zugriff „aus Versehen“ wird vermieden:

$allUsers = App_User::getAllUsers();
Zend_Registry::set('allUsers', $allUsers);

Der Zugriff würde dann so aussehen:

Zend_Registry::get('allUsers');

Das würde zwar funktionieren, ist aber ziemlich unpraktisch, da man nie weiß, wo genau der erste Zugriff ist, man also nicht genau weiß, ob die Informationen bereits in der Zend_Registry sind oder nicht. Das bedeutet viele if-Abfragen und ist unhandlich. Also verschieben wir den „Cache“ etwas weiter nach „innen“, wir verändern die getAllUsers()-Funktion wie folgt:

public static function getAllUsers() {
	if (!Zend_Registry::isRegistered('allUsers')) {
		$db = Zend_Registry::get('Zend_Db');
		$allIds = $db->fetchCol('SELECT UserID FROM User');
		
		$users = array();
		foreach ($allIds as $id) {
			$users[] = new App_User($id);
		}
		Zend_Registry::set('allUsers', $users);
	}
	
	return Zend_Registry::get('allUsers');
}

Nun wird also beim Aufruf von getAllUsers() beim ersten Mal die Datenbank abgefragt, und das Ergebnis in der Zend_Registry gespeichert. Beim zweiten Aufruf wird nun das bereits gespeicherte Ergebnis genommen. Wir sparen uns also viele Datenbankabfragen und Objekterstellungen. Von „außen“ kann man die Funktion ganz normal verwenden, man merkt nicht, dass intern gecacht wird.

Zwischenstand: Wir können innerhalb eines Scriptes viele Abfragen sparen, indem wir Ergebnisse und Objekte in der Zend_Registry speichern und diese bei Bedarf wiederverwenden.

Doch wir können noch mehr an Performance gewinnen, indem wir prozessübergreifend cachen. Wenn also 10 Besucher innerhalb von 5 Sekunden auf unserer Webseite unterwegs sind, sollen diese wenn möglich die selben Daten teilen, sodass für diese 10 Besucher nur einmal die 5000 Datensätze abgefragt und die entsprechenden Objekte erstellt werden müssen. Das geht nun nicht mehr mit globalen Variablen bzw. Zend_Registry, man muß mittels gemeinsamen Speichers (zB Memcached-Server, Festplatte, Netzspeicher) diese Daten austauschen. Diese gemeinsamen Daten sollen allerdings nach einer gewissen Zeit „ungültig“ werden, sodass regelmäßig frische und aktuelle Daten aus der Datenbank geholt werden. Genau das alles kann Zend_Cache.

Zend_Cache besteht grundlegend aus zwei Schichten: Dem Frontend und dem Backend. Das Backend definiert man nur einmal am Anfang, indem man den gewünschten Storage wählt und spezifiziert. Zur Auswahl stehen derzeit: File, Sqlite, Memcached, Apc, Xcache, ZendPlatform, TwoLevels, ZendServer_Disk
Die gebräuchlichsten dürften die ersten vier sein.

Wenn wir nun beispielsweise Zend_Cache_Backend_File wählen, müssen wir nur den Dateipfad angeben, die anderen Einstellungen können wir vorerst vernachlässigen.

Das Frontend ist die Schicht, über die wir den eigentlichen Cache ansprechen. Hier stehen uns mehrere Möglichkeiten zur Verfügung: Wir können beispielsweise einfache Variablen cachen, aber auch ganze Funktionen, Klassen, Dateien oder Seiten. Wir wollen uns hier erstmal nur um Variablen kümmern, die anderen Dinge könnt ihr euch ja im ZF-Manual nachlesen.

Nun aber Butter bei die Fische:

$frontendOptions = array(
   'lifetime' => 60, // cache lifetime of 1 minute
   'automatic_serialization' => true
);

$backendOptions = array(
    'cache_dir' => './tmp/' // Directory where to put the cache files
);

// getting a Zend_Cache_Core object
$cache = Zend_Cache::factory('Core',
                             'File',
                             $frontendOptions,
                             $backendOptions);
Zend_Registry::set('Zend_Cache', $cache);

Hier haben wir nun den Cache erstellt. Hier sieht man auch, dass man eine Lifetime definieren kann. Liegt ein Element länger als eine Minute im Cache, wird es gelöscht und muß dementsprechend neu aus der Datenbank geholt werden.

public static function getAllUsers() {
$cache = Zend_Registry::get(‚Zend_Cache‘);
// see if a cache already exists:
if(!$allUsers = $cache->load(‚allUsers‘)) {
// cache miss; connect to the database
$db = Zend_Registry::get(‚Zend_Db‘);
$allIds = $db->fetchCol(‚SELECT UserID FROM User‘);

$allUsers = array();
foreach ($allIds as $id) {
$allUsers[] = new App_User($id);
}
$cache->save($allUsers, ‚allUsers‘);
}

return $allUsers;
}
Wie man sieht, es ist der Zend_Registry Lösung sehr ähnlich. Es braucht nicht mehr als 20 Zeilen, um Caching zu aktivieren und zu nutzen. Nun haben wir die Freiheit, das Backend zu wählen, die Lebensdauer der Elemente zu spezifizieren, und bei Bedarf kann man auch Tags setzen. Tags sind vor allem dazu da, alle Elemente mit einem bestimmten Tag gleichzeitig zu löschen. Näheres dazu auch im ZF-Manual.

Ich hoffe man sieht, dass man durch Caching ordentlich Performance gewinnen kann, und sowohl die Besucher als auch die Hardware schonen kann. Natürlich macht Caching nicht überall Sinn (bei einem Ajax-Chat wäre es wohl eher hinderlich), aber die meisten Inhalte ändern sich nicht sekündlich, sondern eher in größeren Zeitabständen, und wenn ein Besucher einige Minuten „veraltete“ Inhalte zu sehen bekommt, ist das nicht unbedingt schlimm.

Morgen poste ich einen Artikel, der auch zu diesem Themenkomplex passt, aber das ganze von der anderen Seite betrachtet.

Written by Michael Kliewe

Juli 9th, 2009 at 6:50 pm

Posted in PHP

Tagged with , ,

Nicht-HTML-Responses mit dem Zend Framework

with 5 comments

Wenn man dynamische Bilder oder RSS-Feeds oder einen AJAX/JSON-Service oder ein Excel-Export mithilfe des Zend Frameworks erstellen will, mußt man 2-3 wichtige Dinge beachten. Der Code soll dann in einem Rss-/Graph-/Ajax-/Export-Controller stehen.

Ein Problem bekommt man, wenn man ein Layout benutzt (Zend_Layout). Denn dann wird dieses Layout immer ausgegeben. Im hier betrachteten Fall wäre das aber sehr schädlich, denn dadurch würden wir unser Bild/RSS-Feed/AJAX/Excel-Response zerstören.

Unschön kann man das wie folgt lösen:

public function rssAction()
{
	// calculate rss data and echo it (with correct headers)

	exit;
}

Richtig und deutlich schöner ist das Abschalten des Layout in der Action, wie folgt:

public function rssAction()
{
	// disable layout
	$this->_helper->layout()->disableLayout();
	
	// disable view rendering
	$this->_helper->viewRenderer->setNoRender();
	
	// calculate rss data and echo it (with correct headers)
}

Wir schalten auch gleich noch den ViewRenderer mit aus, damit auch nicht versucht wird, ein Viewscript zu rendern (das es wahrscheinlich garnicht gibt).

Dieses RSS-Beispiel funktioniert natürlich genauso für die anderen Beispiele, wo kein klassischer HTML-Quelltext zurückgegeben werden soll, sondern eine Antwort in einem anderen Format gefordert ist.

Damit kann man dann seine dynmisch erstellten Bilder (z.B. mittels pChart, jpgraph oder direkt die GD-Funktionen/image* in php), RSS-Feeds (Zend_Feed), Ajax-Services (Zend_Json) usw. realisieren.

Hier noch schnell ein Beispiel eines Excel-Exports aus einer Datenbank, mit Hilfe der Spreadsheet-Klasse aus dem PEAR-Framework (vereinfacht auf das Wesentliche):

class ExportController extends Zend_Controller_Action
{	
	public function excel() {
		// disable layout
		$this->_helper->layout()->disableLayout();
		
		// disable view rendering
		$this->_helper->viewRenderer->setNoRender();

		
		// get some data from database here
		
		       
		// create empty file
		//include 'Spreadsheet/Excel/Writer.php';
		$excel = new Spreadsheet_Excel_Writer();
		// add worksheet
		$sheet =& $excel->addWorksheet('Daily Export');

		$sheet->setColumn(0,0,20);
		$sheet->setColumn(1,1,15);
		$sheet->setColumn(2,2,18);
		$sheet->setColumn(3,3,23);
		$sheet->setColumn(4,4,35);
		$sheet->setColumn(5,5,15);

		$format_bold =& $excel->addFormat();
		$format_bold->setBold();

		$format_headline =& $excel->addFormat();
		$format_headline->setBold();
		$format_headline->setSize(20);
		$format_headline->setAlign('center');

		// headline
		$sheet->write(0, 0, 'Results: '.date('d.m.Y H:i'), $format_headline);
		$sheet->mergeCells(0,0,0,5);

		// add data to worksheet
		$rowCount=2;

		foreach ($data as $groupName=>$serverData) {
			$sheet->write($rowCount, 0, $groupName, $format_bold);
			$rowCount++;

			foreach ($serverData as $row) {
				$colcount = 0;
				foreach ($row as $key => $value) {
					$sheet->write($rowCount, $colcount, $value);
					$colcount++;
				}
				$rowCount++;
			}
			$rowCount++;
		}
		// send client headers
		$excel->send('daily_export_'.date("Ymd-His").'.xls');
	}		
}

Dies hier ist alter Code, mittlerweile nutzen wir PHPExcel.

Written by Michael Kliewe

Juli 2nd, 2009 at 2:52 pm

Posted in PHP

Tagged with , , , , , ,

PHP und das Zend Framework

without comments

Wer öfter programmiert, wird früher oder später auf Frameworks zurückgreifen. Frameworks, das sind Sammlungen von häufig genutztem Programmcode, heutzutage für jede ernsthafte Programmiersprache erhältlich und ziemlich mächtig. Sie können einem durchaus 50% der Zeit ersparen, denn viele Dinge braucht man immer wieder, und genau diese Dinge sind mit guten Frameworks abgedeckt. Frameworks bieten einem auch einen gewissen Rahmen, um schönen strukturierten Code zu schreiben, zum Beispiel durch die Ordnerstruktur oder die Art uns Weise, wie das MVC-Pattern umgesetzt wird.

Ist Zend Framework (ZF) das einzige Framework für PHP? Beileibe nicht! Es ist eines von 4 oder 5 großen und verbreiteten Frameworks, existiert seit April 2006 und die Entwicklung wird vom „PHP-Hersteller“ Zend getrieben. Vorher war das wohl bekannteste Framework das PEAR Framework. Andere Frameworks sind beispielsweise die ziemlich neuen Projekte Yii und Flow3, aber auch symfony, ez Components und CakePHP könnte man schonmal gehört haben. Eine vollständige Liste mit allen PHP Frameworks findet sich zB hier: www.phpframeworks.com

Welches man nun benutzt bleibt einem selbst überlassen (bzw. wird vom Arbeitgeber/Kunden vorgeschrieben), man sollte sie sich aber zumindestens kurz anschauen und die Quick-Guides durchsehen, um einen Eindruck zu bekommen. Oder man fragt gleich den PHP-Experten seiner Wahl, was er empfehlen würde.

In der Firma sind wir hier vor ca. einem Jahr auf das Zend Framework umgestiegen. Vorher haben wir viele PEAR-Klassen genutzt, und uns mit Hilfe von „frontend, backend und template-Ordnern“ selbst eine rudimentäre MVC Basis gebastelt, da PEAR kein MVC-Konzept bietet. Das war auf jeden Fall ein großer Schritt nach vorn, mittlerweile haben wir alle PEAR-Klassen sogut es geht ersetzt durch die entsprechenden ZF-Pendants, aber ganz „losgeworden“ sind wir PEAR noch nicht. Noch ein Grund, mehrere Frameworks zu kennen, denn ein Framework, das alles kann, gibt es nicht (und wird es auch nicht geben).

Beim Zend Framework wird es natürlich auch gern gesehen, Fehler zu melden (im ZF Issue Tracker) und eventuell sogar selbst aktiv Code beizutragen, denn das ganze Projekt ist natürlich Open Source, und es gibt einen gut dokumentierten Weg, eigenen Code oder sogar eigene Klassen beizutragen. Bugfixes kommen natürlich am besten direkt in den Issue Tracker, wohingehen neue Funktionalitäten und Klassen verschiedene Stadien durchlaufen müssen, um schlussendlich aufgenommen zu werden. Dazu gehört am Anfang das Proposal, also der erste Entwurf und die Vorstellung für die Allgemeinheit. Nachdem andere Entwickler ihren Senf dazugegeben haben, kommt der Code in das Laboratory, dann den Incubator, und dann irgendwann hoffentlich in den offiziellen Zweig. Recht aufwändig, aber durchaus nötig, damit auch nur nötiger und qualitativ hochwertiger Code ins Framework gelangt.

Ich als Entwickler freue mich aber natürlich auch über die vorherigen Schritte, denn auch dort gibt es bereits Code, den man im Notfall verwenden kann, wenn man diese oder jene Funktionalität unbedingt benötigt. Die Liste der Proposals ist wirklich ansehnlich, ihr könnt es ja mal überfliegen und euch einen Eindruck holen: ZFPROP

Zusammenfassend sei gesagt, dass es mir extrem gut gefällt, wie das Zend Framework organisiert ist, dadurch macht PHP noch mehr Spass als vorher.

Written by Michael Kliewe

Juni 11th, 2009 at 12:29 pm

Posted in PHP

Tagged with ,