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.
ghost
Dez 01, 2009 @ 15:45:37
Hmm … Warum sollte man das tun? :P
Rudi
Dez 01, 2009 @ 16:04:29
Man braucht das zum Beispiel, wenn man eine Factory Klasse hat, die Objekte erzeugt, welche nach dem Erzeugen aber nur noch read-only sein sollen (oberes Beispiel).
Ansonsten kann man das bestimmt noch wo anders verwenden. oO
Rudi
Feb 15, 2010 @ 23:27:31
OK, man braucht es doch nicht. In folgendem Forum gibt es ein schöneres Gegenbeispiel:
http://www.php.de/php-fortgeschrittene/46607-casting-php5.html
Naja … vielleicht stößt der ein oder andere mal auf ein entsprechendes Problem.
Cem Derin
Dez 01, 2009 @ 16:22:07
Hatte sowas auch mal gebaut, allerdings konnte man so Objekte komplett mutieren lassen. Es musste gar keine Ableitung sein. Leider ist mein Blog grade down, wenns wieder da ist, werde ich mich nochmal melden ;)
Rudi
Dez 01, 2009 @ 16:45:33
Jo, ich kann mich an deinen Artikel erinnern. Du hattest es über Serialisierung, String-Replacement und dann wieder Deserialierung gemacht, richtig?
War bestimmt noch bevor es Reflection für PHP gab?! :)
Casting in PHP5 - php.de
Dez 01, 2009 @ 16:40:31
Cem Derin
Dez 03, 2009 @ 08:55:29
Genau: http://phphacker.net/2009/05/14/frankenstein-spielen-objektmutation-mit-php/
War nicht bevor es Reflection gab, sondern ergab sich aus dem Wunsch heraus, ein bestehendes Objekt zu einer Instanz einer völlig anderen Klasse zu machen ;)
Timo Reitz
Dez 03, 2009 @ 23:09:57
1. Wüsste ich nicht, wofür man sowas braucht. Für Read-Only-Objekte aus veränderlichen Objekten gibt es andere Lösungen, Wrapper z.B. oder Builder, die mit einer entsprechenden create()-Methode aufwarten.
2. Um ein Casting handelt es sich nicht, es werden lediglich Eigenschaften kopiert. Theoretisch lässt sich so aus einem Objekt jeder beliebigen Klasse ein Objekt einer beliebigen anderen Klasse erzeugen – ob das so schlau ist?
3. Was ist mit „magic properties“, also Eigenschaften, die im Objekt nicht wirklich vorkommen, sondern über __get(), __set(), etc. simuliert werden?
Rudi
Dez 04, 2009 @ 10:01:46
1. Ok,
vielleichtgibt es für das „ReadOnly Problem“ eineanderebessere Lösung (siehe hier).Welche besser ist, kann ich gerade nicht sagen.Der Wrapper wie du ihn beschrieben hast, ist meiner Meinung nach etwas umständlicher, da alles was erlaubt ist, explizit durchgeschleift werden muss.
Die create() Methode könnte bei vielen Eigenschaften ungemütlich viele Parameter entgegennehmen müssen.
2. Es ist kein „echtes“ Casting. Das hab ich auch gar nicht gemeint. Aber ein Casting dieser Art geht in PHP eben nicht.
Mit der oben beschriebenen Methode kann man keine beliebig andere Klasse erzeugen! Es werden dynamisch nur die Eigenschaften der parent Klasse kopiert. Ist eine Eigenschaft nicht vorhanden, gibt’s Fehler.
3. „Magic Properties“ sind Methoden und müssen damit nicht kopiert werden.
Nils
Dez 05, 2009 @ 07:46:48
Moin zusammen,
ich hatte mich ja schon mal mit Cem darüber unterhalten, wann das ganze Sinn macht oder nicht. Leider sehe ich immer noch nicht den Sinn eines solchen „downcasts“. Wieso sollte ich wollen, dass mein Objekt die Klasse wechselt. OOP ist doch gerade dazu da, dass sie jede abgeleitete Klasse wie die Elternklasse verhält (Liskovsches Substitutionsprinzip, falls man den so schrebt),
Wer sagt denn, dass die Attribute der parent Klasse die gleichen Inhalte repräsentieren? Falls sie doch genau das gleiche repräsentieren, dann kann ich genau so gut die Kindklasse nehmen. Falls nicht, macht das ganze natürlich auch nicht so viel Sinn :)
Lasse mich aber wie immer gerne vom Gegenteil überzeugen. Ach ja, als Spielerei finde ich solche Dinge natürlich sehr interessant!
Gruß,
Nils
Rudi
Dez 07, 2009 @ 12:46:34
Echtes casten funktioniert nur, wenn der Zieltyp einen gleichen parent-typ hat bzw. von der selben Elternklasse erbt. Damit sollte das Liskovsche Substitutions Prinzip nicht verletzt sein, weil sich beide Objekte wie deren gemeinsame Elternklasse verhalten (sollten). Hier ist nicht die Rede von Objekt-Mutationen über Klassengrenzen hinaus.
Wie im obigen Beispiel kann man down-casten verwenden, wenn man zusätzliche Funktionen einer Kindklasse wieder einschränken möchte.
Ansonsten wird auch noch gecastet, wenn eine Methode einen anderen Subtyp erwartet, z.B. liegt ein Wert als Integer vor, soll aber als double übergeben werden.
Was ich ehrlich gesagt nicht beachtet habe, sind private Properties. Diese müssen ja keinen Zusammenhang in den jeweiligen Klassen haben. Vielleicht sollte man diese beim kopieren ausschließen.
Ansonsten gehören protected und public Properties meiner Meinung nach genauso zum Verhalten einer Klasse, wie deren die Methoden.