Objekte dynamisch instantiieren

5 Kommentare

Gestern bin ich auf das Problem gestoßen, dass ich Objekte nicht dynamisch instantiieren kann, so ähnlich wie es bei Funktionen und Methoden mit „call_user_func_array“ funktioniert.

Also hab ich eine eigene Funktion geschrieben, die den Klassennamen und ein Array aller Parameter übergeben bekommt. Daraus wird dann der Codeschnipsel erzeugt, der das Objekt erstellt. Dieser Codeschnipsel wird dann mit eval ausgeführt und das neue Objekt zurückgegeben.
Das ganze sieht dann so aus:

function createInstance($className, array $arguments = null) {
	if ($arguments == null) {
		$arguments = array();
	}
	
	// generate instantiate code
	$instantiateCode = '$object = new '.$className.'(';
	if (sizeof($arguments) > 0) {
		foreach($arguments AS $key => $arg) {
			$instantiateCode .= '$arguments["'.$key.'"], ';
		}
		$instantiateCode = preg_replace('/, $/', ');', $instantiateCode);
	} else {
		$instantiateCode .= ');';
	}
	
	// instantiate and return object
	eval($instantiateCode);
	return $object;
}

Falls jemand eine coolere Idee hat, dann her damit. :)

Der Anwendungszweck ist übrigens folgender:
Ich hab eine Library mit der ich gewisse Basisfunktionalitäten anbiete. Wenn jemand nun etwas verändern möchte, ohne die Updatefähigkeit zu verlieren, erstellt er eine neue Klasse mit der neuen Logik und lässt diese von der entsprechende Standardklasse erben. Damit die neue Logik jetzt auch an allen anderen Stellen im Code verwendet wird, müsste man überall den neuen Klassennamen eintragen, das will aber keiner.
Also hab ich die genannte „createInstance()“ Methode gebaut, die vor dem instantiieren überprüft ob eine Klasse mit dem Präfix „Custom_“ existiert. Wenn dem so ist, wird das Objekt der Kindklasse erzeugt. (Das Präfix muss vorher definiert sein.)

Objekte in PHP down-casten

11 Kommentare

Heute bin ich auf ein fehlendes OOP Feature von PHP gestoßen: „Down-Casten“ funktioniert nicht.
Ich weiß nicht, ob down-casten der korrekte Begriff ist, aber so hab ich ihn in einem PHP Forum gefunden.
Es geht darum, ein Objekt einer abgeleiteten Klasse in ein Objekt der Übergeordneten (parent) Klasse zu casten. So ungefähr sollte das eigentlich aussehen:

class ReadRecord {
  protected $value;
  private function __construct(){}
  public function getValue(){
    return $this->value;
  }
}

class WriteRecord extends ReadRecord {
  public function __construct(){}
  public function setValue($v){
    $this->value = $v;
  }
}

$wr = new WriteRecord();
$wr->setValue("hello world");
$rr = (ReadRecord) $wr;

Geht aber nicht! Man bekommt einen parse error: „syntax error, unexpected T_VARIABLE“. Doof.
Also hab ich drüber nachgedacht, wie man das trotzdem hin bekommt und bin dank der seit PHP5 vorhanden Reflection API auf folgende Lösung gekommen:

class ReadRecord {
  protected $value;
  protected function __construct(){}
  public function getValue(){
    return $this->value;
  }
}

class WriteRecord extends ReadRecord {
  public function __construct(){}
  public function setValue($v){
    $this->value = $v;
  }
  public function toReadRecord(){
    $rr = new ReadRecord();
    
    $reflectedReadRecordClass = new ReflectionClass("ReadRecord");
    $props = $reflectedReadRecordClass->getProperties();
    foreach($props AS $prop){
       $propName = $prop->getName();
       $rr->$propName= $this->$propName; 
    }
    return $rr;
  }
}

$wr = new WriteRecord();
$wr->setValue("foo");
$rr = $wr->toReadRecord();
//$rr->setValue("bar"); //Fatal error: Call to undefined method ReadRecord::setValue() in ...
echo $rr->getValue(); //prints "foo"

Das ganze ist vielleicht etwas unperformant, aber es geht!
Theoretisch könnte man auch eine abstrakte Klasse schreiben welche die „downcast“ Methode über Reflection allgemein implementiert, aber das finde ich unschön. So wie im oberen Beispiel kann man kontrolliert down-casten.

Why should a PHP skript die?

1 Kommentar

Vor einigen Tagen wurde in einigen Blogs [1][2] diskutiert, ob die Funktion/Sprachkonstrukt die() bzw. exit() sinnvoll ist, wie man sie einsetzen sollte oder ob sie eigentlich aus PHP entfernt werden kann. Hab mich dazu näher mal damit beschäftigt und möchte folgende Verhaltensweise festhalten:

Ist zum Zeitpunkt des Aufrufs von die/exit ein instantiierten Objekt vorhanden, wird noch die „__destruct()“ Methode aufgerufen. Das gilt für jedes Objekt zu diesem Zeitpunkt. Das Skript ist also noch nicht 100%ig beendet: Es kann noch Code ausgeführt werden!
Diese Tatsache wollte ich dazu verwenden, eine Exception in einem Destruktor auszulösen welche den Exit Befehl aufhebt/überspringt. Somit könnte man mit der Instantiierung eines „ExitDestuktors“ die Verwendung von die/exit komplett unterbinden und man hätte eine schöne auffangbare Exception:

class ExitDestructor{
  public function __destruct(){
    throw new Exception("immortality");
  }
}
$exitDestructor = new ExitDestructor();
try {
  die();
} catch (Exception $ex) {
  //handle exception
}

Leider hat das nicht hingehauen. Vielleicht geht es ja mit PHP 5.3+ mit einem goto Befehl x). PHP meldet auf jeden Fall „Fatal error: Exception thrown without a stack frame in Unknown on line 0„. Dieser Fehler würde übrigens auch dann auftreten, wenn ein Skript Ordnungsgemäß beendet wird, was auch nicht gewollt wäre.
» Experiment fehlgeschlagen.

Schade. Wäre ein netter „dieToExeptionWrapper“ geworden. Da sollte man sich echt Gedanken machen, wozu man die/exit überhaupt einsetzten soll. Mir ist es schon in folgenden sinnvollen Zusammenhängen über den Weg gelaufen:
1. Meta Redirect aus dem Skript heraus auslösen: die(‚<meta http-equiv=“refresh“ content=“0; URL=‘.$url.'“>‘); – Das könnte man aber auch mit einer „RedirectException“ lösen.
2. Nach dem direkten Senden einer Datei an den Browser. Danach sollte nämlich kein einziges Byte mehr gesendet/ausgegeben werden! (Davor übrigens auch nicht.)

Der zweite Punkt ist der einzige Anwendungsfall der mir eingefallen ist, bei dem eine Exception eher weniger weiterhelfen würde! Dann ist die/exit dann also doch gerettet? Oder erhebt jemand Einspruch?