Goodbye Manager’s Life

Vor über 5 Jahren habe ich Manager’s Life ins Leben gerufen, einerseits um ein cooles Browserspiel zu bauen, andererseits um (damals) neue Techniken/Technologie wie HTML5 2D Canavas auszuprobieren. Aus dieser Zeit sind auch einige Blog-Beiträge entstanden (zB: Sprechblasen mit HTML5 Canvas). Das Spiel war/ist technisch ziemlich cool, allerdings habe ich zu wenig Zeit in ein gutes Konzept und eine gute Spielmechanik gesteckt. Dadurch ist das Spiel nie richtig ins Rollen gekommen. Zwar habe ich es im Hintergrund immer weiter laufen lassen, aber heute ist Zeit das Spiel offline zu nehmen. Danke an alle Spieler und an alle die mitgeholfen haben Manager’s Life zu entwickeln.

Der Quellcode des Spiels ist auf Github verfügbar: agrafix/managerslife.

 

Sehr simpler Captcha in PHP – Captcha.class.php

Dieser Beitrag ist Teil meiner Sourecode a Day-Aktion.

Heute möchte ich eine Klasse vorstellen, mit der man sehr einfache Captchas generieren und prüfen kann. Man sollte diese Klasse so jedoch nicht direkt verwenden, sondern zumindest statt imagestring imagettftext mit einer eigenen Schriftart verwenden.

<?php
/**
 * a simple captcha
 *
 * @author Alexander Thiemann 
 */
class Captcha {
	/**
	 * generate new captcha code
	 *
	 */
	public static function reinit() {
		$_SESSION["xcaptcha"] = rand(10000, 99999);
	}
	
	/**
	 * check the userinput + captcha
	 *
	 * @param string $code
	 * @return bool
	 */
	public static function check($code) {
		if (isset($_SESSION["xcaptcha"]) && !empty($code) && $code == $_SESSION["xcaptcha"]) {
			return true;
		}
		else {
			return false;
		}
	}
	
	/**
	 * generate captcha image
	 *
	 */
	public static function generate() {
		$im = ImageCreate(55, 20);
		$white = ImageColorAllocate($im, 0xFF, 0xFF, 0xFF);
		$black = ImageColorAllocate($im, 0x00, 0x00, 0x00);
		ImageString($im, 4, 3, 3, $_SESSION["xcaptcha"], $black);
		
		header("Content-Type: image/png");
		ImagePNG($im);
		ImageDestroy($im);
	}
}
?>
 

CURL Wrapper-Klasse in PHP – WebBrowser.class.php

Dieser Beitrag ist Teil meiner Sourecode a Day-Aktion.

Eine simple nützliche Wrapper-Klasse, die den einfachen Umgang mit CURL ermöglicht. Cookies und Referer werden automatisch gespeichert.

<?php
/**
 * Simple CURL Wrapper
 *
 * @author Alexander Thiemann 
 */
class WebBrowser {
	/**
	 * saves curl session
	 *
	 * @var cURL
	 */
	private $ch = null;
	
	/**
	 * saves referer
	 *
	 * @var string
	 */
	private $ref = "";
	
	/**
	 * saves results
	 */
	public $result = "";
	
	/**
	 * init curl
	 *
	 */
	public function __construct() {
		$this->ch = curl_init();
	}
	
	/**
	 * destruct
	 *
	 */
	public function __destruct() {
		curl_close($this->ch);
	}
	
	/**
	 * post
	 *
	 * @param string $url
	 * @param string $data
	 */
	public function post($url, $data) {
		$this->result = "";
		curl_setopt($this->ch, CURLOPT_URL, $url);
		curl_setopt($this->ch, CURLOPT_POST, 1);
		curl_setopt($this->ch, CURLOPT_POSTFIELDS, $data);
		$this->refer($url);
		$this->setopts();
		ob_start();  
		$this->result = curl_exec($this->ch);
		ob_end_clean();
	}
	
	/**
	 * get
	 *
	 * @param string $url
	 */
	public function get($url) {
		$this->result = "";
		curl_setopt($this->ch, CURLOPT_URL, $url);
		$this->refer($url);
		$this->setopts();
		ob_start();  
		$this->result = curl_exec($this->ch);
		ob_end_clean();
	}
	
	/**
	 * update referer
	 *
	 * @param string $url
	 */
	private function refer($url) {
		curl_setopt ($this->ch, CURLOPT_REFERER, $this->ref);
		$this->ref = $url;
	}
	
	/**
	 * set global opts
	 *
	 */
	private function setopts() {
		curl_setopt($this->ch, CURLOPT_FOLLOWLOCATION, 1);
		curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, true);
		// you may want to change this
		curl_setopt($this->ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.0.9) Gecko/2009040821 Firefox/3.0.9 (.NET CLR 3.5.30729)");
		curl_setopt($this->ch, CURLOPT_COOKIEJAR, "cookie");
		curl_setopt($this->ch, CURLOPT_COOKIEFILE, "cookie");
	}
}
?>

Die Verwendung:

$browser = new WebBrowser();
// einfaches get request; Resultat in Datei speichern
$browser->get("http://google.de");
$fp = fopen("tmp.txt", "w+");
fwrite($fp, $browser->result);
fclose($fp);

// post request
$browser->post("http://localhost/post.php", "test_data=" . urlencode("asdfg"));
// response is in $browser->result
 

Galgenmännchen KI – Hangman.class.php

Dieser Beitrag ist Teil meiner Sourcecode a Day Aktion.

Vor langer Zeit habe ich mir mal eine kleine Klasse zur Lösung von Galgenmännchen-Spielen geschrieben. Man benötigt zur Verwendung der Klasse eine Datei wordlist.txt, in der möglichst viele deutsche Wörter gespeichert sind!

<?php
/**
 * Solve a Hangman game
 *
 * @author Alexander Thiemann 
 */
class Hangman {
	/**
	 * asked letters
	 *
	 * @var array
	 */
	public $asked_letters = array();
	
	/**
	 * possible words
	 *
	 * @var array
	 */
	public $possible_worlds = array();
	
	/**
	 * construct hangman ;)
	 *
	 * @param string $state
	 */
	public function __construct($state) {
		$lines = file("wordlist.txt");
		
		foreach($lines As $line) {
			$n = strtolower(trim($line));
			if ($this->WordCompare($state, $n)) {
				$this->possible_worlds[] = $n;
			}
		}
	}
	
	/**
	 * fetch suggestion
	 *
	 * @return string
	 */
	public function getSuggestion() {
		$w = "enisratdhulcgmobwfkzpvjyxq"; // change this if you dont use german words!!
		for($i=0;$iasked_letters) && $this->LetterInWords($w{$i})) {
				$this->asked_letters[] = $w{$i};
				return $w{$i};
			}
		}
	}
	
	/**
	 * check if letter is usefull
	 *
	 * @param string $letter
	 * @return bool
	 */
	public function LetterInWords($letter) {
		
		foreach ($this->possible_worlds As $w_out) {
			if (strpos($w_out, $letter) !== false) {
				return true;
			}
		}
		
		return false;
	}
	
	/**
	 * compare two words, * is placeholder
	 *
	 * @param string $word
	 * @param string $compare
	 * @return bool
	 */
	public function WordCompare($word, $compare) {
		$ln = strlen($word);
		
		if ($ln != strlen($compare)) {
			return false;
		}
		
		for ($i=0;$i

Ein Beispiel zur Verwendung:

if (!isset($_GET["state"])) {
	$state = "***";
}
else {
	$state = $_GET["state"];
}

$Man = new Hangman($state);

if (isset($_GET["letters"])) {
	$Man->asked_letters = explode("|", $_GET["letters"]);
}
echo "Es sind noch ".count($Man->possible_worlds)." Kombinationen möglich.";
if (count($Man->possible_worlds) < 50) {
	echo "
".implode(", ", $Man->possible_worlds); } echo "
"; echo $Man->getSuggestion(); echo "
"; echo "
State: asked_letters)."' />
";
 

Einfache Datenbank-Klasse für PHP

Dieser Beitrag ist Teil meiner Sourecode a Day-Aktion.

Ich möchte eine Klasse vorstellen, die ich mir mal geschrieben habe um einfach auf mySQL-Datenbanken zuzugreifen. Ich verwende sie selbst allerdings nicht mehr, da CodeIgniter eine eigene Datenbank-Abstraktion liefert und die meisten PHP-Hosts jetzt auch mysqli unterstützen!

<?php
/**
 * mySQL Database Class
 *
 * @author Alexander Thiemann 
 */
class RealSQL {
	/**
	 * db connector
	 *
	 * @var resource
	 */
	protected $conn;
	
	/**
	 * save last query
	 *
	 * @var string
	 */
	protected $last_query = "";
	
	/**
	 * save query results
	 *
	 * @var array
	 */
	protected $results = array();
	
	/**
	 * Store variables, eg. sql replacements
	 *
	 * @var array
	 */
	private $stored_variables = array();
	
	/**
	 * constructor -> connect
	 *
	 * @param string $host
	 * @param string $user
	 * @param string $pass
	 * @param string $db
	 */
	public function __construct($host, $user, $pass, $db) {
		$this->conn = @mysql_connect($host, $user, $pass);
		
		if (!$this->conn) {
			$this->error();
		}
		
		if (!@mysql_select_db($db, $this->conn)) {
			$this->error();
		}
	}
	
	/**
	 * execute query
	 *
	 * @param string $query
	 * @param int $id
	 */
	public function query($query, $id=0) {
		if (strpos($query, "INSERT INTO") !== false) {
			if (!@mysql_unbuffered_query($query, $this->conn)) {
				$this->last_query = $query;
				$this->error();
			}
		}
		else {
			$this->results[$id] = @mysql_query($query, $this->conn);
			
			if (!$this->results[$id]) {
				$this->last_query = $query;
				$this->error();
			}
		}
	}
	
	/**
	 * fetch as array/numrows/etc
	 *
	 * @param int $id
	 * @param string $fetchtype
	 */
	public function fetch($id=0, $fetchtype="") {
		switch(@$fetchtype) {
			case "num_rows":
				return mysql_num_rows($this->results[$id]);
				break;
				
			default:
				$ass = mysql_fetch_assoc($this->results[$id]);
				
				return $ass;
				break;
		}
	}
	
	/**
	 * fetch all in one array
	 *
	 * @param int $id
	 */
	public function tpl_fetch($id=0) {
		$array = array();
		
		while ($r = $this->fetch($id)) {
			$array[] = $r;
		}
		
		return $array;
	}
	
	/**
	 * insert stuff into db
	 *
	 * @param array $data_array
	 * @param string $table
	 */
	public function insert($data_array, $table) {
		$fields = array_keys($data_array);
		
		foreach ($data_array As $k => $v) {
			if (is_numeric($v)) {
				$data_array[$k] = (double)$v;
			}
			else {
				$data_array[$k] = "'".urlencode($v)."'";
			}
		}
		
		$query = "INSERT INTO `".$table."` (`".implode("`,`", $fields)."`) VALUES (".implode(", ", $data_array).")";
		
		$this->query($query);
	}
	
	/**
	 * update fieldset
	 *
	 * @param array $data_array
	 * @param string $table
	 * @param string $where
	 */
	public function update($data_array, $table, $where) {
		
		$update = array();
		
		foreach ($data_array As $k => $v) {
			if (is_numeric($v)) {
				$data_array[$k] = (int)$v;
			}
			else {
				$data_array[$k] = "'".urlencode($v)."'";
			}
			
			$update[] = "`$k` = ".$data_array[$k];
		}
		
		$query = "UPDATE `$table` SET ".implode(", ", $update)." WHERE $where";
		
		$this->query($query);
	}
	
	/**
	 * set param (automatic urlencode+stripslashes+')
	 *
	 * @param string $key
	 * @param string $value
	 */
	public function setParam($key, $value) {
		if (is_string($value)) {
			$i = "'".urlencode(stripslashes($value))."'";
		}
		elseif (is_numeric($value)) {
			$i = $value;
		}
		else {
			$i = "'".urlencode(stripslashes($value))."'";
		}

		$this->stored_variables[$key] = $i;
	}

	/**
	 * delete all params
	 *
	 */
	public function clearParams() {
		$this->stored_variables = array();
	}

	/**
	 * execute paramed query
	 *
	 * @param string $sql
	 * @param int $id
	 */
	public function exec($sql, $id=0) {
		foreach($this->stored_variables AS $key => $val) {
			$sql = str_replace("[$key]", $val, $sql);
		}

		$this->query($sql, $id);
	}
	
	/**
	 * returns last ai-id
	 *
	 * @return int
	 */
	public function lastId() {
		return mysql_insert_id($this->conn);
	}	
	
	/**
	 * error handler
	 *
	 */
	protected function error() {
		// uncomment this line 
		// if you have deployed the app
		#die("Internal Server Error."); 
		
		echo "
"; echo "

RealSQL Error Occured

"; if ($this->last_query != "") { echo "

Query

".htmlspecialchars($this->last_query).""; } echo "

Error

".htmlspecialchars(mysql_error($this->conn)); echo ""; print_r(debug_backtrace()); echo ""; echo "
"; exit; } } ?>

Die Verwendung:

$db = new RealSQL("localhost", "root", "", "test_db");

// einfach eine Query ausführen
$db->query("UPDATE user SET username = 'asdf' WHERE id = 1");

// query ausführen und Ergebnisse holen
$db->query("SELECT * FROM user", 1);

while ($row = $db->fetch(1))
{
	echo "Benutzer: " . $row['username'];
}

// neuen Eintrag in die DB
$db->insert(array('username' => 'test'), 'user');

// Eintrag ändern
$db->update(array('username' => 'banana'), 'user', 'id=2');

// Query mit Parametern
$db->setParam("user", $_POST['user']);
$db->setParam("pass", md5($_POST['pass']));

$db->exec("SELECT * FROM user WHERE username = [user] AND password = [pass]");

$db->clearParams();

Die aller erste Manager’s Life Version verwendete diese Klasse.

 

Mehrsprachigkeit für PHP-Projekte: aLang.class.php

Dieser Beitrag ist Teil meiner Sourecode a Day-Aktion.

Heute möchte ich eine Klasse vorstellen, mit der man sehr einfach Mehrsprachigkeit in sein PHP-Projekt bringen kann:

section = $section;
		$this->lang = $language;
		
		if (!empty($path)) {
			$this->path = $path;
		}
		
		$this->parse();
	}
	
	/**
	 * parse the language file
	 *
	 */
	function parse() {
		$filename = $this->path.$this->lang.".ini";
		$cachedata = $this->path.$this->lang.".cachedata";
		$cachearray = $this->path.$this->lang.".cachearray";
		
		if (!file_exists($filename)) {
			die("aLang Error: Language File for $this->lang doesn't exist!");
		}
		
		// caching system
		$ini_size = filesize($filename);
		
		if (file_exists($cachedata) && file_exists($cachearray)) {
			$cachesize = implode ('', file ($cachedata));
			
			if ($ini_size != $cachesize) { // reparse
				$this->reparse($filename);
			}
			else { // load from cache
				$serialized = base64_decode(implode('', file($cachearray)));
				$this->parsed = unserialize($serialized);
			}
		}
		else { // reparse
			$this->reparse($filename);
		}
	}
	
	/**
	 * parse ini file and write cache
	 *
	 * @param string $fname
	 */
	function reparse($fname) {
		$this->parsed = parse_ini_file($fname, true);
		$ini_size = filesize($fname);
		
		$fp = @fopen($this->path.$this->lang.".cachedata", "w+");
		@fwrite($fp, $ini_size);
		@fclose($fp);
		
		$fp = @fopen($this->path.$this->lang.".cachearray", "w+");
		@fwrite($fp, base64_encode(serialize($this->parsed)));
		@fclose($fp);
	}
	
	/**
	 * grab translation
	 *
	 * @param string $varname
	 * @return string
	 */
	function get($varname) {
		if (!isset($this->parsed[$this->section][$varname])) {
			die("aLang Error: $this->section[$varname] not found!");
		}
		return $this->parsed[$this->section][$varname];
	}
	
	/**
	 * grab translation out of specified section
	 *
	 * @param string $section
	 * @param string $varname
	 * @return string
	 */
	function grab($section, $varname) {
		if (!isset($this->parsed[$section][$varname])) {
			die("aLang Error: $section[$varname] not found!");
		}
		
		return $this->parsed[$section][$varname];
	}
}
?>

In dieser Konfiguration müssten die Sprachdateien nun in den Ornder lang/. Beispiel für eine de.ini:

[main]
hello=Hallo
test=Das ist ein kleiner Test

Verwendung:

$lang = new aLang("main", "de");
echo $lang->get("hello"); // prints Hello
echo "
"; echo $lang->get("test"); // prints Das ist ein kleiner Test

Die Klasse wird übrigens in abgewandelter Form in der aktuellen TWLan-Version verwendet 😉

 

ReTI IDE & Simulator (Technische Informatik)

Da ich aktuell an der Veranstaltung Technische Informatik an der Uni teilnehme war es im letzten Übungsblatt Aufgabe Programm-Code für einen einfachen Prozessor (ReTI oder auch RESA) zu schreiben. Das vorgeschlagene Tool dafür (Neumi) kann entsprechenden Programm-Code zwar super visualisieren, ist allerdings beim „coden“ nicht sehr hilfreich da es kein vernünftiges Feedback bei Fehlern gibt und es auch keine gute Eingabemaske für den Code gibt. Deshalb hab ich mich hingesetzt und eine kleine HTML-JavaScript-IDE mit einer ReTI-Simulation in PHP zusammengebaut. Das ganze ist unter reti.agrafix.net zu finden! Ich habe auch eine kleine Dokumentation dazu geschrieben, es sollte sich also alles von selbst erklären.
Ein paar Worte vielleicht noch zur „Simulation“: Die Simulation ist eher ein Debugger, da der Code nicht assembliert sondern direkt interpretiert wird. Das ist notwendig um zielgenaues Feedback bei (Laufzeit-)Fehlern zu geben. Deshalb ist es nicht möglich auf Speicherzellen zuzugreifen, die Anweisungen/Befehle enthalten. (Mit dieser Einschränkung konnte man dennoch alle Aufgabenstellungen bis jetzt lösen 😉 ) Solange man das also nicht benötigt, kann der Simulator den korrekten Endspeicherzustand berechnen.

Zur ReTI IDE & Simulator

Für die HTML-JavaScript-IDE bzw. dessen Code-Editor habe ich übrigens auf das CodeMirror 2 Framework gebaut. Super Framework, leider muss man erstmal ein bisschen Dokumentation lesen bis man damit zu guten Ergebnisen kommt!

 

Poker Abstraktion

Wie bereits angekündigt wird die nächste Version von Manager’s Life ein Casino in die Spielwelt integrieren. Dabei darf natürlich kein Multiplayer-Poker fehlen!

Poker mit PHP umzusetzen ist eigentlich eine relativ einfache Sache – der schwierigere Teil ist das erkennen der Poker-Hände beim Showdown (Flush, Straight, Pair, …). Natürlich kann man einfach alle möglichen Hände hardcoden und dann einfach eine nach der anderen Abarbeiten (siehe zB phppokerengine), das ist aber nicht besonderst elegant, performant oder erweiterbar. Außerdem ist es sehr unübersichtlich und fehleranfällig. Deshalb habe ich mir gedacht, man könnte alle möglichen Poker-Hände mit einer Abstraktions-Sprache definieren und diese dann mit entsprechendem Parser zu behandeln.

Zuerst zu meiner Sprache, die ich wie folgt definiert habe:

  • Gleiche Zahlen stellen gleiche Karten-Werte dar
  • Gleiche Buchstaben stellen gleiche Farben dar
  • Ein Fragezeichen steht für eine beliebige Karte
  • Ein > sagt, dass die nächste Karte direkt auf die vorherige folgen muss.
  • Ein [ sagt, dass das nächste Zeichen kein Symbol, sondern kein expliziter Kartenwert ist
  • {x} nach einem Symbol gibt an, wie oft es wiederholt wird

Um das ganze zu konkretisieren habe ich nun alle Poker-Hände in meine Sprache übersetzt:

// pair (zB ♥A - ♦A)
1{2}

// two pair (zB ♣A - ♦A - ♠3 - ♣3)
1{2}2{2}

// three of a kind (zB ♣A - ♦A - ♠A)
1{3}

// straight (zB ♣K - ♦Q - ♠J - ♣10 - ♣9)
?>?>?>?>?

// flush (zB ♣2 - ♣Q - ♣9 - ♣10 - ♣9)
a{5}

// full house (zB ♣K - ♦K - ♠Q - ♣Q - ♣Q)
1{3}2{2}

// four of a kind (zB ♣K - ♦K - ♠K - ♣K)
1{4}

// straight flush (zB ♣K - ♣Q - ♣J - ♣10 - ♣9)
a>a>a>a>a

// royal flush (zB ♣A - ♣K - ♣Q - ♣J - ♣10)
[a>a>a>a>a

Das Parsen und Verarbeiten ist zwar etwas trickreicher, aber machbar. Ich habe einen Quick-n-Dirty PHP-Prototypen gebaut – er funktioniert, ist allerdings nicht besonderst sauber geschrieben. Im Vergleich zu der Methode „alle-Möglichkeiten-hardcoren“ entsteht übrigens noch ein weiterer Vorteil: Meine Klasse ist 7kB groß, die oben verlinkte 0,5 MB…

Die Verwendung der Klasse erfolgt so:

a>a>a>a');

$hand = array(
  array('color' => 'spades', 'card' => 'k'),
  array('color' => 'spades', 'card' => 'q'),
  array('color' => 'spades', 'card' => 'j'),
  array('color' => 'spades', 'card' => '10'),
  array('color' => 'spades', 'card' => '9')
);

if ($parser->check($hand)) {
   echo "Deine Hand enthält ein Straight Flush!
n"; } else { echo "Kein Straight Flush vorhanden.
n"; } ?>

Die Ausgabe sollte „Deine Hand enthält ein Straight Flush!“ sein.

Die Klasse findet sich dort: PokerParser.class.php

 

RedBean PHP – Top oder Flop?

Eine Datenbank-Abstraktion mit ORM kann sehr praktisch sein und viel Arbeit sparen, vorallem wenn sie so funktioniert wie RedBean-PHP.
Nun, wie funktioniert RedBean-PHP? Eigentlich relativ simpel. Anderst als bei sehr bekannten PHP-ORM-Libraries wie Doctrine oder Propel muss sich der Programmierer theoretisch gar nicht um das Layout der Datenbank kümmern. Er muss also keine Felder oder Relationen vor dem eigentlichen Programmieren definieren, sondern kann sie einfach verwenden, und RedBean-PHP kümmert sich um den Rest. Klingt ziemlich cool, oder? Hier mal ein Beispiel:

// RedBean-PHP besteht aus einer einzigen Datei
require "rb.php";

// Zur Datenbank verbinden:
// derzeit wird SQLite, MySQL und PostgreSQL unterstützt
R::setup('mysql:host=localhost;dbname=redbeantest','root','');

// ein haus erstellen
$house = R::dispense('house');
$house->color = 'blue';
$house->size = 34;
R::store($house); // in der db speichern

// einen besitzer erstellen
$owner = R::dispense('owner');
$owner->name = 'Alex';
R::store($owner); // in der db speichern

// das Haus gehört dem Besitzer
// Many-to-Many Relation, da jeder Besitzer 
// viele Häuser haben kann und ein Haus 
// mehrere Besitzer
R::associate($owner, $house);

// eine stadt erstellen
$city = R::dispense('city');
$city->name = 'Zauberstadt';

// eine One-to-Many Relation, da 
// jedes Haus nur zu einer Stadt 
// gehört
$city->ownHouse[] = $house; 

R::store($city); // speichern

// jetzt laden wir mal ein paar sachen aus der DB
// einen besitzer mit dem name Alex finden
$person = R::findOne('owner', ' name = ?', array('Alex'));

// seine häuser finden
$houses = R::related($person, 'house');
var_dump($houses); // häuser dumpen

// die häuser der stadt ausgeben
foreach ($city->ownHouse as $key => $house) {
   echo "Haus Nummer $key ist ".$house->color."n";
}

Das ganze funktioniert, sobald es eine Datenbank „redbeantest“ gibt. Es müssen keine Tabellen in der Datenbank erstellt werden, noch Forgein-Keys angelegt werden. (Weitere Beispiele und die Dokumentation sind auf der Webseite.)

Alles wunderbar, allerdings kommt hier schon ein erstes Problem auf! RedBean PHP stellt nämlich alle Forgein-Keys und dessen Relations auf ON UPDATE SET NULL, ON DELETE SET NULL. Dieses Verhalten ist aber nicht immer gewünscht, das heißt hier muss man aufjedenfall noch nach RedBean-PHP aufräumen bevor die Software in Produktion gehen kann. Ein weiteres Problem ist, dass RedBean-PHP den Typ eines Datenbankfelds nur dann optimal wählt, wenn man es mit genügend Test-Daten füttert. Ansonsten ist auch hier ein nachträgliches Aufräumen nötig. Hier kann man sich also die Frage stellen – möchte ich lieber später meine Datenbank aufräumen müssen, oder sie direkt von Anfang an korrekt designen?

Ein zweites Problem von RedBean-PHP ist Performance. Das Framework macht, vorallem bei Relations, einfach viel zu viele Datenbank-Abfragen, die bei vielen Zugriffen zu hoher Datenbanklast führen können (Abhilfe schafft hier ein Caching System, allerdings ist das nur hilfreich, wenn keine aktuellen Daten benötigt werden). Hier endet man dann also damit einige R::find und R::related durch eigene Queries zu ersetzen. (was übrigens auch prima funktioniert, aber dann nicht mehr im Sinne von ORM ist…)

Abgesehen davon ist es aber eine super Sache, man kommt aufjedenfall schnell zu Ergebnissen. Ich würde es für alle kleineren PHP-Projekte empfehlen! Für größere Projekte würde ich davon abraten, da man einfach zu schnell den Überblick verliert und man mit dem „Aufräumen“ schnell mal etwas übersieht was dann zu kritischen Fehlern führen kann.

 

HTML5 WebSocket

HTML5 unterstützt jetzt das Aufbauen von „WebSocket“-Verbindungen. Eine WebSocket Verbindung basiert ähnlich wie HTTP auf dem TCP Protokoll, baut aber eine „dauerhafte“ Verbindung auf. Die Vorteile gegenüber HTTP ist, dass eine Aktion des Servers keine vorhergehende Anfrage des Clients mehr erfordert, wodurch der Server neue Informationen fasst in Echtzeit ausliefern kann. Dadurch kann man eine Menge Traffic und viele unnötige HTTP-Requests sparen, die etwa ein AJAX-JavaScript sonst schicken würde um neue Informationen zu erhalten.
Technisch gesehen baut bei einer WebSocket-Verbindung der Browser mit dem Server eine Verbindung auf, die nach einen Handshake dann bestehen bleibt und über die dann Binär- und Textdaten geschickt werden können.

Leider hat das Protokoll im Handshake einige Sicherheitsprobleme (gehabt?) und sich deshalb ständig verändert. Dieser Artikel bezieht sich also auf die Aktuellste Version (17). Diese wird derzeit von der aktuellsten Chrome Version und anderen Webkit Browsern unterstützt, in Firefox und Opera ist WebSocket wegen der Sicherheitsprobleme derzeit deaktiviert.

Um HTML5 WebSocket zu verwenden ist zunächst ein WebSocket-Server nötig. Ich habe hier nach einer PHP-Variante gesucht und habe dann nach einer Weile folgenden gefunden: php-websocket. Diesen sollte man sich laden und dann kann man ihn per

php -q php-websocket/server/server.php

starten. Jetzt ist ein WebSocket-Server auf „localhost:8000“ zu erreichen. Dort sind derzeit zwei Module geladen, einmal das Modul „echo“ und einmal das Modul „chat“. (Ich werde mich hier nicht mit der Entwicklung eines WebSocket-Server weiter befassen) Das „echo“ Modul nimmt einfach Anfragen entgegen und schickt diese dann an alle Verbundenen Sockets weiter.

Nun kümmern wir uns um die HTML5-Seite von WebSocket, dem User-Frontend. Dafür bauen wir uns erstmal eine einfache HTML5 Seite:





HTML5 WebSocket Test





// hier wird dann das WebSocket-Script reinkommen, siehe unten




Log

Chatbox

Say something

Für die einfache Verwendung des WebSockets habe ich mir eine kleine Wrapper-Library (websocketlib.js) programmiert:

/**
 * Small wrapper library for HTML5 WebSocket
 * 
 * @author Alexander Thiemann 
 * @version 1.0
 * 
 * @param string WebSocket host, eg. ws://localhost:8010/echo
 * @param function Function to be called when socket is connected
 * @param function Function to be called when socket recieves data, takes 1 argument message
 * @param function Function to be called for logging purposes, takes 1 argument log-message
 * 
 * @see http://blog.agrafix.net/2011/11/html5-websocket/
 */
var WebSocketLib = {
	init: function(host, onConnect, onRecv, onLog) {
		this.socket = new WebSocket(host);
		
		this.onLog = onLog;
		this.onRecv = onRecv;
		this.onConnect = onConnect;
		
		this.onLog('Initializing Socket. State: ' + this.socket.readyState);
		
		this.socket.onopen = this._onOpen;
		this.socket.onmessage = this._onMessage;
		this.socket.onclose = this._onClose;
	},
	
	send: function(message) {
		WebSocketLib.socket.send(message);
	},
	
	_onClose: function() {
		WebSocketLib.onLog('Socket Closed.');
	},
	
	_onMessage: function(msg) {
		WebSocketLib.onLog('Recieved new Message from Server: ' + msg.data);
		WebSocketLib.onRecv(msg.data);	
	},
	
	_onOpen: function() {
		WebSocketLib.onLog('Socket is Ready. State: ' + WebSocketLib.socket.readyState);
		
		if (WebSocketLib.socket.readyState == 1) {
			WebSocketLib.onConnect();
		}	
	}
}

Ich denke dort ist alles relativ selbsterklärend. Das WebSocket ist wirklich sehr leicht zu verwenden! Um diese Wrapper-Library zu verwenden, bauen wir in obiges HTML-Grundgerüst bei „// hier wird dann das WebSocket-Script reinkommen, siehe unten“ folgendes ein:

$(document).ready(function() {
	WebSocketLib.init('ws://localhost:8010/echo', 
	function() {
		sockReady();
	},
	function (t) {
		$('#chatbox').append($("

").text(t)); }, function (t) { $('#logbox').append($("

").text(t)); }); function sockReady() { $('#chat').submit(function(e) { e.preventDefault(); var input = $('input[name="message"]'); WebSocketLib.send(input.val()); input.val(""); }); } });

Zuerst warten wir, bis unser HTML-Dokument im Browser aufgebaut ist. Dann geben wir in Zeile 2 an, wo unser WebSocket-Server läuft. Die Funktion in Zeile 4 wird aufgerufen, sobald das WebSocket verbunden ist. Die Funktion Zeile 6-8 wird aufgerufen, wenn eine Nachricht zurückgegeben wird. Die Funktion Zeile 9-11 wird bei Log-Ereignissen (Hauptsächlich zum Debuggen) aufgerufen. Mit WebSocketLib.send(„Nachricht“) in Zeile 19 schicken wir eine Nachricht an den Server.

Wenn nun alles vorbereitet ist, der WebSocket-Server gestartet ist (siehe oben), können wir einem Browser, der WebSocket unterstützt (siehe oben), unsere HTML-Seite aufrufen. Die Log-Ausgabe im HTML-Fenster sieht wie folgt aus:

Initializing Socket. State: 0
Socket is Ready. State: 1

Im Consolenfenster des PHP-Server sehen wir:

2011-11-25 11:26:47 [info] [client 127.0.0.1:30601] Connected
2011-11-25 11:26:47 [info] [client 127.0.0.1:30601] Performing handshake
2011-11-25 11:26:47 [info] [client 127.0.0.1:30601] Handshake sent

Die Verbindung ist nun also aufgebaut. Jetzt sollten wir den „echo“-Server testen. Wir tippen etwas in das Textfeld und schicken es ab. Der Log sagt:

Recieved new Message from Server: test

Und unter „Chat“ steht jetzt „test“. Hat soweit also funktioniert! Jetzt können wir ein neues Browserfenster aufmachen, wieder den Verbindungsvorgang abwarten und dann dort eine Nachricht verschicken. Diese erscheint dann in allen verbundenen Client-Fenstern.

Eigentlich ziemlich einfach zu verwenden, und es öffnet viele neue Möglichkeiten vorallem in Kombination mit Canvas für HTML5-Multiplayer-Games… hoffen wir also, dass es bald von allen Browser voll unterstützt wird! 🙂