mmofacts.com

PHP5: Vererbung

gepostet vor 18 Jahre von Dead Silence
Folgende Situtation:
abstract class A {

function methode() {
echo get_class($this) . "\n";
}
static function statische_methode() {
echo __CLASS__ . "\n";
echo get_class() . "\n";
}
}
class B extends A {
}

echo "B->methode():\n";
$i = new B();
$i->methode();
echo "\nB::statische_methode():\n";
B::statische_methode();
?>
Ausgabe:
B->methode():

B
B::statische_methode():
A
A
Wie krieg ich beim statischen Aufruf die korrekte Klasse? Ist das so überhaupt möglich?
gepostet vor 18 Jahre von exe
Statische Methoden werden auch Klassenmethoden genannt. Das heisst, sie sind immer ihrer Klasse zugeordnet. Wenn du also eine statische Methode ausführst wird sie in dem Kontext der Klasse ausgeführt, in der sie definiert ist. Mit Vererbung oder Instanzen hat das nicht so arg viel zu tun.
Die Ausgabe von PHP ist da also richtig.
gepostet vor 18 Jahre von Dead Silence
Original von exe
Statische Methoden werden auch Klassenmethoden genannt. Das heisst, sie sind immer ihrer Klasse zugeordnet. Wenn du also eine statische Methode ausführst wird sie in dem Kontext der Klasse ausgeführt, in der sie definiert ist.

Und genau das ist mein Problem. Ich möchte auf den Kontext der aufrufenden Klasse zugreifen. Beim nicht-statischen Aufruf bekommt man mit $this eine Referenz auf die aufrufende Instanz und dementsprechend suche ich beim statischen Aufruf einen Hinweis auf die aufrufende Klasse.
Mit Vererbung oder Instanzen hat das nicht so arg viel zu tun.

Die Ausgabe von PHP ist da also richtig.
Das war eigentlich nur zur Verdeutlichung, was ich will und wie es nich geht.
gepostet vor 18 Jahre von Agmemon
Original von Dead Silence
...dementsprechend suche ich beim statischen Aufruf einen Hinweis auf die aufrufende Klasse.

Könntest Du vielleicht mal schreiben, wofür Du die Information brauchst? Ich finde Deinen Code etwas seltsam. Du deklarierst eine Klasse abstrakt, obwohl Sie es nicht ist. Und du möchtest die aufrufende Klasse einer Klassenmethode wissen, die ja eigentlich bekannt ist. Ansonsten könntest Du die Methode je nicht aufrufen.
gepostet vor 18 Jahre von Dead Silence
Original von Agmemon
Könntest Du vielleicht mal schreiben, wofür Du die Information brauchst?

Ich habe eine Basisklasse A und eine Reihe davon abgeleiteter Klassen B, C, ..., die alle eine statische Methode tu_was() implementieren. Die Methode ist für alle Klassen praktisch identisch und unterscheidet sich nur durch einen von der Klasse abhängigen Parameter.
Ich wollte dies eigentlich folgendermaßen realisieren:
abstract class A {

const K = "A";
static function tu_was() {
echo $aufrufende_klasse::K . "\n";
}
}
class B extends A {
const K = "B";
}

class C extends A {
const K = "C";
}
Natürlich könnte ich jetzt hergehen und in jeder abgeleiteten Klasse die Methode überschreiben:
abstract class A {

const K = "A";
static function tu_was($k = self::K) {
echo $k . "\n";
}
}
class X extends A {
const K = "X";
static function tu_was()
{
parent::tu_was(self::K);
}
}
Aber das ist nicht wirklich das gelbe vom Ei.
Ich finde Deinen Code etwas seltsam.

Mag daran liegen, dass es ein Beispiel ist
Du deklarierst eine Klasse abstrakt, obwohl Sie es nicht ist.

Wenn ich eine Klasse als abstrakt deklariere, dann ist sie per Definition abstrakt. Das heisst ja nicht mehr, als dass man keine Instanz davon erzeugen kann.
Und du möchtest die aufrufende Klasse einer Klassenmethode wissen, die ja eigentlich bekannt ist. Ansonsten könntest Du die Methode je nicht aufrufen.

Die aufrufende Klasse ist nicht bekannt, da es sowohl die Basisklasse als auch eine beliebige abgeleitete Klasse sein kann. Selbst in meinem ersten Beispiel ist es aus Sicht der Methode nicht eindeutig, ob sie nun von A oder B aufgerufen wird.
gepostet vor 18 Jahre von exe
Ich will mal das PHP-Manual zitieren:
Tatsächlich werden static Methodenaufrufe zum Kompilierungszeitpunkt aufgelöst. Bei der Nutzung des expliziten Klassennamens ist die Methode bereits gänzlich identifiziert und es kommen keine Vererbungsregeln zur Anwendung.

Selbst wenn du B::statisch() aufrufst wird das intern in A::statisch() übersetzt. Im Grunde gibt es den Kontext der aufrufenden Klasse gar nicht, da die statische Funktion ohne Umwege aufgerufen wird.
Es würde auch nicht viel Sinn ergeben das anders zu machen. Da statische Methoden zu ihrer Klasse gehören werden (oder sollten sie zumindest) nicht mit vererbt. Das bedeutet auch, dass wenn du B::statisch() aufrufst in Wirklichkeit A::statisch() aufgerufen wird da statisch() durch die Vererbung nicht in die Klasse B wandert. Das sie über die vererbte Klasse sichtbar werden, also mit B::statisch() aufgerufen werden können, ist dann mehr eine Nettigkeit der Sprache
Natürlich könnte ich jetzt hergehen und in jeder abgeleiteten Klasse die Methode überschreiben:

[...]
Aber das ist nicht wirklich das gelbe vom Ei.
Aber wohl du einzige Möglichkeit für das was du vorhast.
Du könntest natürlich noch in A::statisch() mit der Funktion debug_backtrace() den aktuellen Aufruf zurückverfolgen und damit die aufrufende Klasse herausbekommen. Aber ob das so eine schöne Lösung ist?
gepostet vor 18 Jahre von Agmemon
Wenn ich eine Klasse als abstrakt deklariere, dann ist sie per Definition abstrakt. Das heisst ja nicht mehr, als dass man keine Instanz davon erzeugen kann.

Abstrakt bedeutet schon einiges mehr, als dass keine Instanzen davon erzeugt werden können. Abstrakte Klassen dienen zur Definition von Prototypen, die in der ableitenden Klasse implementiert werden müssen. Aber ich muss gestehen, dass es schon ein kreative Idee ist, die abstrakt Definition dafür zu benutzen, die Instanzierung zu verhindern. Ich hatte den Bedarf zwar noch nie, aber das werde ich mir merken.
Die aufrufende Klasse ist nicht bekannt, da es sowohl die Basisklasse als auch eine beliebige abgeleitete Klasse sein kann. Selbst in meinem ersten Beispiel ist es aus Sicht der Methode nicht eindeutig, ob sie nun von A oder B aufgerufen wird.

Da haben wir uns jetzt missverstanden. Bei statischen Funktionen ist die aufrufende Klasse immer bekannt, da die Funktion immer zu Ihrer Klasse gehört, daher ja auch die Bezeichnung Klassenmethode. Vermutlich benutzt Du nur einen falschen Menchanismus für das, was Du machen willst. Daher auch meine Frage nach einem konkreten Beispiel, denn mir ist Deine Zielsetzung unklar. Wenn Klasse::tuwas nur immer die Klasse zurückgibt, ist das in meinen Augen eine sinnlose Funktion:
A::tuwas gibt A zurück, B::tuwas gibt B zurück, C::tuwas gibt C zurück. Ein sinnloser Methoden Aufruf, da die Klasse ja bekannt ist, da ich sie ja zum Aufrufen der Klassenmethode benötige.
Daher halt die Frage nach einem konkreten Beispiel/Einsatzzweck, um Dir vielleicht helfen zu können, einen geeigneteren Ansatz zu finden.
gepostet vor 18 Jahre von Dead Silence
Original von exe
Ich will mal das PHP-Manual zitieren:
Tatsächlich werden static Methodenaufrufe zum Kompilierungszeitpunkt aufgelöst. Bei der Nutzung des expliziten Klassennamens ist die Methode bereits gänzlich identifiziert und es kommen keine Vererbungsregeln zur Anwendung.

Selbst wenn du B::statisch() aufrufst wird das intern in A::statisch() übersetzt. Im Grunde gibt es den Kontext der aufrufenden Klasse gar nicht, da die statische Funktion ohne Umwege aufgerufen wird.
Es würde auch nicht viel Sinn ergeben das anders zu machen. Da statische Methoden zu ihrer Klasse gehören werden (oder sollten sie zumindest) nicht mit vererbt. Das bedeutet auch, dass wenn du B::statisch() aufrufst in Wirklichkeit A::statisch() aufgerufen wird da statisch() durch die Vererbung nicht in die Klasse B wandert. Das sie über die vererbte Klasse sichtbar werden, also mit B::statisch() aufgerufen werden können, ist dann mehr eine Nettigkeit der Sprache
Ich würde diese "Nettigkeit der Sprache" schon als Anwendung der Vererbungsregeln bezeichnen Aber natürlich hast du Recht: Die Methode wandert nicht von Klasse A nach B, sondern der Methoden-Aufruf wandert - bildlich gesprochen - von Klasse B nach A und die Methode wird im Kontext von Klasse A ausgeführt.
Du könntest natürlich noch in A::statisch() mit der Funktion debug_backtrace() den aktuellen Aufruf zurückverfolgen und damit die aufrufende Klasse herausbekommen. Aber ob das so eine schöne Lösung ist?

Das funktioniert leider auch nicht
Original von Agmemon

Daher halt die Frage nach einem konkreten Beispiel/Einsatzzweck, um Dir vielleicht helfen zu können, einen geeigneteren Ansatz zu finden.
Ich versuche eine Art Object-Relational-Mapping mit PDO als Grundlage zu realisieren. PDO bietet ja schon die Möglichkeit, Ergebniszeilen direkt als Objekte zu beziehen. Allerdings muss man die Logik (select, update,...) dieser Objekte selber implementieren und dafür wollte ich eine Basisklasse DBObject schreiben.
Davon abgeleitete Klassen Object1, Object2 unterscheiden sich z.B. bei einem SELECT vom Grundsatz her nur durch den SQL-String, die Logik bleibt die gleiche. Darum wollte ich die Methode select() so in der Basisklasse implementieren, dass diese den SQL-String aus der abgeleiteten Klasse ließt:
abstract class DBObject {

static function select()
{
$stmt = self::$db_handler->prepare($aufrufende_klasse::SQL_SELECT);
$stmt->execute();

$stmt->setFetchMode(PDO::FETCH_CLASS, $aufrufende_klasse);
$objects = $stmt->fetchAll();
$stmt = null;

return $objects;
}
}
class Object1 extends DBObject {
const SQL_SELECT = 'SELECT * FROM objects1';
}

class Object2 extends DBObject {
const SQL_SELECT = 'SELECT * FROM objects2';
}
Soweit zumindest die Theorie...
gepostet vor 18 Jahre von schokofreak
Das macht sehr wohl Sinn, was du möchtest.
Habe auch schon so gearbeitet, sit sehr praktisch.
Was mir einfach nicht klar ist, wieso hast du die funktion select Statisch gemacht?
Das wäre etwas, was in meinen Augen absolut keinen Sinn macht?
Gruss
gepostet vor 18 Jahre von Dead Silence
Jede Instanz von DBObject (oder einer abgeleiteten Klasse) soll eine Zeile in einer Tabelle repräsentieren. Eine Instanz kann aber erst erzeugt werden, nachdem die entsprechende(n) Zeile(n) aus der Datenbank gelesen wurde(n).
Und genau das ist die Aufgabe der Methode select(): Sie liest die Zeilen und gibt diese als Array von Objekten zurück, d.h. die Objekte werden in select() erst instanziert.
insert(), update() und delete() beziehen sich dagegen jeweils auf eine bestimmte Instanz/Zeile und sind daher nicht-statisch. Nur da komm ich über get_class($this) und Reflections an die entsprechenden Klassen-Konstanten, von daher waren die als Beispiel eher ungeeignet
gepostet vor 18 Jahre von schokofreak
Alles klar.
Löse das ganze doch einfach so, dass du eine Klasse für Tabellen, sowie eine Klasse für Records hast?
Dann benötigst du kein solches "gewürge" mehr.
Das Tabellenobjekt erstellt dann beim Select beliebig viele Objekte der Records.
Gruss
gepostet vor 18 Jahre von Dead Silence
Hmm, das ist aber auch irgendwie Gefrickel Das Ganze soll in erster Linie einfach zu benutzen sein. Und unter "einfach zu benutzen" verstehe ich nicht, dass man pro Tabelle statt einer nun zwei Klassen definieren und (im schlechtesten Fall) vor jedem select() ein ansonsten unnützes Objekt instanzieren muss.
Nein, ich werde das so lösen, dass man für ein select() direkt die Basisklasse benutzt und den Namen der gewünschten Klasse übergibt. Also z.B.
DBObject::select('Object1', ...);

Ist zwar nicht ganz so elegant, aber IMHO noch die beste Lösung. Und der Benutzer hat dann immernoch die Möglichkeit die Methode in der abgeleiteten Klasse entsprechend zu überschreiben.
gepostet vor 18 Jahre von schokofreak
Dead Silence:
Es ist ganz einfach. Entweder du machst OOP, dann machst du es vernünftigerweise mit zwei klasse, wobei meine Vermutung ist dass du zumindest DREI Klassen benötigst, Oder du machst es nicht mit OOP.
Aber ein Gewürge mit Statischen Funktionen, die keine Sind, würd ich niemals machen...
gepostet vor 18 Jahre von Dead Silence
Könntest du das etwas näher erläutern? Was spricht dagegen, eine Funktion, die aus Sicht der Klasse als Konstruktor dient, als statische Methode dieser Klasse zu realisieren?
gepostet vor 18 Jahre von schokofreak
Ganz einfach: Eine Klasse hat EINE und genau EINE funktion.
ENTWEDER:
- die Klasse repräsentiert eine Tabelle, hat somit Logik zum Abrufen von Datensätzen (mit filter), einzelnem Datensatz aufgrund von ID, Generierung von SQL zu Selects, Updates, Inserts, Deletes
- ODER die Klasse repräsentiert einen Record. Hält somit die Daten des Records (die einzelnen Felder) und weiss zu welcher Tabelle sie gehört, leitet änderungen an die Tabellenklasse weiter, welche SQL Generiert und die Tabelle pflegt.
Aber NIEMALS beides zusammen in einer klasse. Es sind zwei Probleme, zwei Funktionen, somit auch zwei Klassen.
Alles andere, wenn eine Klasse mehr als eine Funktionalität zur Verfügung stellt, führt zu schlecht strukturiertem, unlesbaren, unbrauchbarem Code.
Was zur Konsequenz hat dass die Fehler massiv ansteigen.
Statische Funktionen als Konstruktoren sind absolut in Ordnung. Aber bitte nicht in dem Zusammenhang.
Gruss
gepostet vor 18 Jahre von Dead Silence
Original von schokofreak
Ganz einfach: Eine Klasse hat EINE und genau EINE funktion.
ENTWEDER:
- die Klasse repräsentiert eine Tabelle, hat somit Logik zum Abrufen von Datensätzen (mit filter), einzelnem Datensatz aufgrund von ID, Generierung von SQL zu Selects, Updates, Inserts, Deletes
- ODER die Klasse repräsentiert einen Record. Hält somit die Daten des Records (die einzelnen Felder) und weiss zu welcher Tabelle sie gehört, leitet änderungen an die Tabellenklasse weiter, welche SQL Generiert und die Tabelle pflegt.

So einfach ist das dann doch nicht. Du beschreibst eine objektorientierte Umsetzung eines relationalen Datenmodells. Nur noch eine DatabaseConnection-Klasse hinzufügen und es ist komplett. In einem objektorientierten Datenmodell gibt es aber keine Datenbanken oder Tabellen sondern nur Objekte.
Eine Tabelle kann man wohl am ehesten mit einer Klasse vergleichen. Beide definieren die zugrundeliegende Datenstruktur. Dementsprechend wird ein Datensatz durch eine Instanz der Klasse repräsentiert. Einzig die Datenbankverbindung passt nicht ganz ins Bild, ist aber auch kein allzu großes Problem.
Und genau das möchte ich umsetzen: ein objektorientiertes Datenmodell, in dem die Daten in einer relationalen Datenbank gespeichert werden.
Aber NIEMALS beides zusammen in einer klasse. Es sind zwei Probleme, zwei Funktionen, somit auch zwei Klassen.

Alles andere, wenn eine Klasse mehr als eine Funktionalität zur Verfügung stellt, führt zu schlecht strukturiertem, unlesbaren, unbrauchbarem Code.
Was zur Konsequenz hat dass die Fehler massiv ansteigen.
Es handelt sich aber nur um eine Problem: Datenhaltung. Natürlich kann man kleinere Teilfunktionen in einzelnen Klassen umsetzen und diese Funktionalität dann per Vererbung zur Verfügung stellen.
Statische Funktionen als Konstruktoren sind absolut in Ordnung. Aber bitte nicht in dem Zusammenhang.

Aus Sicht eines Objekts IST ein select() ein Konstruktor.

Auf diese Diskussion antworten