PHPGangsta - Der praktische PHP Blog

PHP Blog von PHPGangsta


Gewinner des Buches steht fest

with 9 comments

So, ich habe es doch noch geschafft, der Gewinner der Verlosung steht fest. Fix einen schönen Zufallsgenerator geschrieben (natürlich in PHP mit dem Zend Framework, Dojo und ein bischen Javascript), und dann das ganze auch noch aufgenommen. Die php-Dateien gibts natürlich auch zum Download, falls ihr auch mal einen so schönen Zufallsgenerator braucht 😉

(Also das .swf bzw .flv, was mein Programm generiert hat, ist ja mal zum fürchten. Mit welchen Programmen nehmt ihr Desktop-Flash-Movies auf?) Hier gibts das avi als Download (Rechtsklick -> Speichern unter).

[flv:https://www.phpgangsta.de/wp-content/uploads/randomizer.flv 702 544]

——-

Der Gewinner: Ghost! (Die Nummer 2). Herzlichen Glückwunsch!

Bitte schick mir deine Adresse via Email rüber an mail, dann schick ich es dir zu.

Danke auch an alle, die mitgemacht haben!

——-

Download des Zufallsgenerators als zip-Datei. Nicht mit reingepackt habe ich das Zend Framework und Dojo, das muß noch an die richtigen Stellen gelegt werden damit es funktioniert (library und public/js )

Written by Michael Kliewe

Juli 14th, 2009 at 1:03 am

Posted in Allgemein

Tagged with , ,

Verlosung des Buches „PHP 5.3 – Die Neuerungen“

with 20 comments

So, nun ist es soweit, ich verlose das vor kurzem von mir gewonnene Buch „PHP 5.3 – Die Neuerungen“ von entwickler.press.PHP 5.3 - Die Neuerungen

Was müßt ihr dafür tun? Eigentlich nicht viel. Da ich keine Adressen sammeln möchte (weder postalische noch Email-Adressen), habe ich mir etwas anderes ausgedacht. Nicht ganz uneigennützig, aber ihr sollt ja auch etwas dafür tun 😉

Um meinen Blog etwas bekannter zu machen, sollt ihr einfach Links zu meinem Blog setzen. Ihr postet dann hier in den Kommentaren den Link, wo ihr den Backlink positioniert habt, und seid im Verlosungstopf drin. Dann habt ihr auch gleichzeitig euren Blog bzw. eure Webseite verlinkt.

Da der Blog noch neu und unbekannt ist, habt ihr sicherlich gute Chancen! Die Verlosung endet Sonntag, 12.07.09 um 24 Uhr. Nils (phphatesme) ist bereits als erster im Topf, da er heute einen Artikel über meinen Blog veröffentlicht hat. Danke dafür!

Ich setze mich dann mit euch in Verbindung.
Also dann mal ran an die „Arbeit“.

Written by Michael Kliewe

Juli 10th, 2009 at 9:58 am

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

Rezension des Buches „PHP 5.3 – Die Neuerungen“

with 2 comments

Nachdem ich am 30.06.2009 via Twitter bei einem Gewinnspiel des Twitter-Users entwicklerpress zwei mal das Buch „PHP 5.3 – Die Neuerungen“ gewonnen habe, habe ich es heute gelesen und möchte meinen ersten schriftlichen Bücher-Review zum Besten geben. Es sei auch noch angekündigt, dass ich in einigen Tagen das zweite Exemplar (es ist natürlich noch orginal verschweißt) hier im Blog verschenken werde. Wie dieses genau stattfindet, werdet ihr dann erfahren.

PHP 5.3 - Die NeuerungenDas Buch heißt wie gesagt „PHP 5.3 – Die Neuerungen“, wurde von Stefan Priebsch verfasst und von entwickler.press verlegt. Es umfasst insgesamt 168 Seiten, und soll uns Entwicklern einen Leitfaden und eine Übersicht geben, was sich mit PHP 5.3 ändert, was es Neues gibt, und welche Funktionalitäten entfernt wurden. Es kostet 12,90 Euro.

Weiterlesen »

Written by Michael Kliewe

Juli 3rd, 2009 at 6:39 pm

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