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.

Advertisements
Dieser Beitrag wurde unter php, webkrams abgelegt und mit , , , , , , , verschlagwortet. Setze ein Lesezeichen auf den Permalink.

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s