Persistenz und Reflexion mit PHP

City Refraction, City Reflection
Attribution License by lrargerich
Reflexion oder Introspektion bedeutet dass ein Objekt seine eigene Struktur kennt und diese Modifizieren kann.
Sehr praktisch ist dies, wenn es um Typsicherheit oder die Persistenz von Daten geht. PHP stellt dafür die Klasse ReflectionClass zur Verfügung. Ich möchte hier beschreiben, wie man mit Reflexion die Typen und Werte von Attributen auslesen kann, um diese z.B. persistent zu halten.
Zuerst ein Beispiel für Reflexion:

class ReflectionExample{
	protected $attribute1;
	protected $attribute2;
	protected $attribute3;
	public 	  $attribute4;

	public function method1(){}
	public function method2(){}

	public function __construct(){
		Reflection::export(new ReflectionClass($this));

	}
}//class
$reflect = new ReflectionExample();

Die Ausgabe:

Class [  class ReflectionExample ] {
  @@ /home/schmiddi/web/gloria2/Reflection.php 8-21

  - Constants [0] {
  }

  - Static properties [0] {
  }

  - Static methods [0] {
  }

  - Properties [4] {
    Property [  protected $attribute1 ]
    Property [  protected $attribute2 ]
    Property [  protected $attribute3 ]
    Property [  public $attribute4 ]
  }

  - Methods [3] {
    Method [  public method method1 ] {
      @@ /home/schmiddi/web/gloria2/Reflection.php 14 - 14
    }

    Method [  public method method2 ] {
      @@ /home/schmiddi/web/gloria2/Reflection.php 15 - 15
    }

    Method [  public method __construct ] {
      @@ /home/schmiddi/web/gloria2/Reflection.php 17 - 20
    }
  }
}

Wie man sieht bekommt man jegliche Information über die Klasse geliefert. Ich möchte wie gesagt Daten bequem in einer Datenbank speichern, bearbeiten oder löschen. Der funktionale oder „PHP Tutorial“ Weg sähe so aus, dass man für jedes Objekt / Aspekt der PHP Seite die SQL Befehle INSERT, UPDATE und DELETE schreibt. Das ist nicht nur sehr Aufwändig, sondern benötigt auch große Aufmerksamkeit, wenn Attribute oder Variablen wegfallen oder neue hinzukommen.

Schauen wir uns noch einmal das Anfangsbeispiel an, um ein kleines Problem mit der Reflection Class zu verdeutlichen:

reflect

Die Klasse Reflect liest die Attribute der erbenden Klasse ReflectMe ein und macht eine Ausgabe:

abstract class Reflect{
	private $types = array();
	private $values= array();
	private $parentAttribute ;
	protected  $parentAttribute2;
	public function getAttributes(){
		$reflection = new ReflectionClass($this);
		$types=$this->types;
		$values=$this->values;
		foreach ($reflection->getProperties() as $key ){
			$value=str_replace("$",' ',$key->name);
			$type =  gettype($this->$value);
			$types[$key->name] = gettype($this->$value);
			$values[$key->name] = $this->$value;
			echo gettype($this->$value).
				" {$key->name} {$this->$value}
";
		}//each
	}//getAtrributes
}//class

Die Kindklasse ReflectMe hat nur ein paar Attribute die ich ausgeben möchte:

class ReflectMe extends Reflect{
	protected $child_attribute1= "Hallo Otto";
	protected $child_attribute2= 3.1415;
	protected $child_attribute3= 1337;
	private	  $child_attribute4= array();

	public function method1(){}
	public function method2(){}

}//class

$reflect = new ReflectMe();
$reflect->getAttributes();

Ein Blick in die Ausgabe verdeutlicht 2 Probleme:

string child_attribute1 Hallo Otto
double child_attribute2 3.1415
integer child_attribute3 1337
NULL parentAttribute2
Fatal error: Cannot access private property ReflectMe::$child_attribute4 in /home/schmiddi/web/gloria2/Reflection.php on line 30

Die ReflectionClass kann nicht auf private Attribute der Kindklasse zugreifen. Für meinen Geschmack ist dieser Grad der Kapselung ein wenig übertrieben. Mit Version 5.3 von PHP wird die Methode ReflectionMethod::setAccessible eingeführt, die den Zugriff erlaubt. Ein weiteres Problem wäre, dass wir auch die protected Werte der Elternklasse bekommen, da diese ja vererbt werden. Dies kann man Umgehen, indem man die Attribute von Reflect private deklariert. Damit schießt man sich jedoch in Sachen Vererbung ins Bein. Eleganter ist es die Elternklasse ebenfalls zu „reflektieren“ und die Schnittmenge der beiden Reflektionen zu entfernen:

/* ... */
	public function getAttributes(){
		//neues ReflectionObject für die Eltern Klasse erzeugen
		$reflectParent = new ReflectionClass(__CLASS__);
		$parentTypes=array();
		//Attribute in ein Array speichern
		foreach ($reflectParent->getProperties() as $key)
			$parentTypes[$key->name] = gettype($this->value);

		$reflection = new ReflectionClass($this);
		$types=$this->types;
		$values=$this->values;
		foreach ($reflection->getProperties() as $key ){
	//nur die Attribute der erbenden Klasse in die Array's schieben
			if(!array_key_exists($key->name, $parentTypes)){
				$value=str_replace("$",' ',$key->name);
				$type =  gettype($this->$value);
				$types[$key->name] = gettype($this->$value);
				$values[$key->name] = $this->$value;
				echo gettype($this->$value).
					" {$key->name} {$this->$value}
";
			}//if
		}//each
	}//getAtrributes

Nun können wir die Attribute der Klasse bequem auslesen, es sei denn sie sind als private deklariert. Für mein Skript möchte ich nicht PHP 5.3 voraussetzen, entsprechend muß darauf geachtet werden, dass nur gespeichert werden kann, was public oder protected ist.
Nun ist alles beisammen, was einen ordentlichen SQL Befehl ausmacht.
Zur Sache also:

private function generateInsertCommand(){
	$this->getAttributes();
		if ($this->insertCommand!=null)
			return $this->insertCommand;

		$classname =  $this->childClassName;
		$values = $this->attributeValues;

		$sql ="
		INSERT INTO  $classname (";
		$counter = 0;
		$count = count($this->attributeValues);
		foreach ($values as $key =>$value){
			$counter+=1;
			$sql.=$key;
			if($counter $count)
				$sql.=',';
		}
		$sql.=")VALUES (";
		$counter = 0;
		foreach ($values as $key =>$value){
			$counter+=1;
			$sql.='?';
			if($counter $count)
				$sql.=',';
		}//each

		$sql.=");";
		$this->insertCommand = $sql;
		return $this->insertCommand ;
	}//function

Die counts sind zwar nicht sehr schön, aber ich brauche sie für die Klammern. Wenn ich mal wieder ein wenig Zeit habe, stelle ich vielleicht den ORM Mapper hier vor.

Fehlerbehandlung mit Singleton Pattern in PHP

Anonymous Single Malt Scotch
Attribution-NonCommercial-ShareAlike License by clappstar

Heute gibt’s mal wieder ein nettes Erzeugungsmuster: das Singleton oder Einzelstück. Mit diesem Pattern kann man verhindern, dass mehr als ein Objekt einer Klasse instanziiert wird. Nützlich ist das Pattern für die Kapselung von Datenbankverbindungen, oder wie in meinem Fall zur Fehlerbehandlung.

Das Prinzip ist schnell erklärt:

Da wir keine neuen Objekt der Klasse erzeugen wollen, ist der Konstruktor uninteressant. Alles was benötigt wird ist eine Referenz auf das einzige erzeugte Objekt. Dafür gibt es die statische Methode getInstance(). In der Methode wird geprüft, ob schon ein Objekt der Klasse vorhanden ist. Falls nicht, wird eine Instanz erzeugt und die Referenz im Klassenattribut $uniqueInstance gespeichert In jedem Fall wird das statische Attribut $uniqueInstance zurückgegeben. .
Der Beispielcode:

class ErrorManager {
	private  $errors, $errorCodes;
	private static $uniqueInstance=NULL;

	private final function __clone(){}
	private function __construct(){
		/** **/
	}

	public static function getInstance(){
		if (self::$uniqueInstance === NULL)
			self::$uniqueInstance = new ErrorManager();
		return self::$uniqueInstance;
	}//getInstance
}

Copy Operator und Konstruktor werden überschrieben, damit niemand durch Vererbung das Pattern aushebeln kann. Die Referenz auf das Objekt können wir uns mit dem Scope Operator besorgen:

$err_manager = ErrorManager::getInstance();

Wer es nicht glaubt kann die Instanz 2 verschiedenen Variablen zuweisen und vergleichen:

$e1 = ErrorManager::getInstance();
$e2 = ErrorManager::getInstance();
if ($e1 === $e2)
    echo "equal";

Soviel zum Singleton Pattern und weiter mit der Fehlerbehandlung. Fehlermeldungen sind immer ein zweischneidiges Schwert, wenn es um Webapplikationen geht. Dem Entwickler helfen sie und irgendwelchen Scriptkiddies leider auch. In der php.ini kann man sie zwar komplett abschalten, aber vielleicht möchte man die Meldungen bestimmten angemeldeten Nutzern nicht vorenthalten. Meine Interpretation des Errorhandlings ist durchaus ausbaufähig aber schon recht brauchbar. Wichtig für die Funktionalität ist das Hochschrauben des Error Reportings:

error_reporting( -1 );

Nun muss ein Errorhandler angegeben werden . Das mache ich im Konstruktor:

	private function __construct(){
			$this->errors = array();
			error_reporting( -1 );
			set_error_handler ( array(__CLASS__, 'errorHandle' ) );
	}

Statt Warnungen und Fehler in die Ausgabe zu schreiben, wird alles nun an die Methode errorHandle übergeben. Ich habe ein Array mit abstrakten Datentypen – AdtError genannt -angelegt. Die Methode erzeugt nun AdtError Objekte und pusht diese in ein Array:

	public static function errorHandle( $code, $msg, $file, $line ){
		$error = new AdtError($code, $msg, $file, $line);
		$s = self::getInstance();
		array_push($s->errors, $error);
	}//errorHandle

Für die Ausgabe hat AdtError die Java-like Methode toString():

	public function getErrors(){
		$s = self::getInstance();
		foreach ($s->errors as $error)
			echo $error->toString().'

';

	}//getErrors

Wenn man nun den Errorhandler anmeldet und ein wenig Unfug schreibt, wird alles wunderbar abgefangen und bei Bedarf ausgegeben:

$err_manager = ErrorManager::getInstance();

foreach ($e_codes as $code => $value)
	echo $code ." ". $value."

";

$err_manager->getErrors();

Bildschirmausgabe:

E_NOTICE Undefined variable: e_codes /home/schmiddi/web/gloria/err_handling/CErrorhandling.php 93
E_WARNING Invalid argument supplied for foreach() /home/schmiddi/web/gloria/err_handling/CErrorhandling.php 93

Wunderbar. Wie schon gesagt gibts durchaus Raum für Verbesserungen. So könnte man der Klasse Abbruchbedingungen beibringen, optional in Textdateien schreiben, schöner Formatieren das Ding in ein MVC Muster hauen usw. Für Interessierte gibts noch den Quelltext: ErrorManager