Sven Rautenberg: OOP php: verschachtelte Klassen und ein Zugriff auf eine Eigensc

Beitrag lesen

Moin!

Könnt ihr mir helfen. Ich programmiere noch nicht lange OO.

Merkt man. Dein OOP-Konzept ist ziemlich grausig!

DAnke für diesen Spruch, Du hochnäsiger Mensch. Ich hätte 100€ verwettet, dass so ein Spruch auf meinen Satz folgt. Du hast ja auch echt ne Menge von meinem Code gesehen, um das sagen zu können.

Wenn du meine Antwort liest mit dem Hintergedanken im Kopf, ich würde dich ganz persönlich angreifen wollen, dann lies sie bitte noch mal ohne diesen Hintergedanken.

Bedenke dabei: ICH habe DICH nicht persönlich angegriffen, ich habe das fachlich kritisiert, was du uns in deinem Posting gezeigt hast. Ich habe die Dinge aufgegriffen, die mir in deinem Ausschnitt sofort ins Auge gefallen sind, die Problematik DEINER Vorgehensweise erklärt und begründet, und Alternativen aufgezeigt.

Wenn das alles in deiner Wahrnehmung mündet in "hochnäsiger Mensch", ist das nicht sachgerecht.

Problematisch in diesem Zusammenhang finde ich auch, dass du in deiner Antwort Texte als Zitatteile einfügst, die ich so nicht geschrieben habe. Sowas gehört sich nicht, wenn man offen und an einer sachlichen Lösung interessiert miteinander umgeht.

Es ist so, das der Beispielcode nur ein Bruchstück darstellt. Und im Kontext macht es mehr Sinn, die DB im Constructor der Site zu instantiieren. Denke ich. Es ist auch nicht nur eine DB, daher mag ich das mit dem Parameter nicht.

Da sind wir genau beim Kern: Ich kritisiere, dass dein Code tut, was er tut. Ich biete Verbesserungsvorschläge an. Du ignorierst diese Vorschläge, weil du selbst glaubst, dass das richtig ist, was du tust. Und quittierst mit "hochnäsiger Mensch".

Es steht dir frei, beliebige Dinge so zu programmieren, wie du willst. Solange das Resultat so funktioniert, wie du willst, ist alles prima.

Erwarte jedoch nicht, dass erfahrene Programmierer das genauso sehen. Wenn du nach Verbesserungsvorschlägen fragst, kriegst du die selbstverständlich. Wenn du aufgrund deines noch nicht so ausgeprägten Erfahrungsschatzes nicht einleuchten will, warum der andere Ansatz besser ist, mach einfach so, wie du denkst - solange, bis deine Erfahrungen dir sagen, dass der andere Ansatz doch besser ist.

Generell gilt bei meinem Projekt: alle Klassen sollen annähernd stand-alone-fähig sein.

Naja, das Problem ist ja nun aber, dass z.B. die Site-Klasse eben NICHT standalone-fähig ist. Sie benötigt ZWINGEND eine Klasse namens "DB" in der verfügbaren Codebasis.

Gibst du der Site im Konstruktor oder in einer separaten Methode ein Datenbankobjekt zur Verwendung, dann muss die Datenbankklasse nicht mehr zwingend "DB" heißen, sie muss nur noch sämtliche Methoden implementieren, die Site aufruft. Und um das sicherzustellen, wird man sehr wahrscheinlich ein Interface definieren, welche als Kontrollinstanz dafür sorgt, dass sämtliche Methoden, die zur Verfügung gestellt werden müssen, auch implementiert sind. Umgekehrt dokumentiert das Interface, welche Methoden man überhaupt benutzen darf, bzw. auf welche Methoden man sich verlassen kann.

Das heisst, jede Klasse kümmert sich erstmal selbst um seine Exceptions.

Das ist halt schon genau das, was ich an deinem Konzept nicht mag. Eine Exception wird geworfen, weil ein nicht umgehbares Problem aufgetreten ist, und der Code an dieser Stelle nicht sinnvoll im vorgesehenen Fahrplan weitermachen kann.

Das wiederum bedeutet, dass die aufgerufene Methode eben gerade NICHT das gewünschte Ergebnis zu liefern imstande ist. Der Codeteil, der diese Methode aufrief, kann also ebenfalls nicht wie geplant weitermachen.

Jetzt gibts eben genau zwei Möglichkeiten: Entweder an dieser Stelle ist im catch-Teil das Notfallprogramm enthalten, welches als Alternative zum Einsatz kommt. Das sollte dann allerdings höchst individuell gestaltet sein, von einem pauschalen Exception-Handling halte ich absolut nichts.

Oder man kann an dieser Stelle kein sinnvolles Notfallprogramm anbieten - dann wird die Exception grundsätzlich nicht gefangen, folglich gibts auch kein try, und wenn sich nicht auf oberster Ebene ein pauschales try/catch doch noch erbarmt, diese Exception etwas userfreundlich in eine nette Browserfehlermeldung mit Status 500 zu wandeln, dann wird PHP mit einer eher hässlichen Fehlermeldung die Programmausführung abbrechen.

Natürlich verwende ich throw, try u. catch. Ich wende es, denke ich, ziemlich sauber an.

Da bin ich anderer Meinung. Ich würde es SO nicht tun. Leider äußerst du dich zu deiner Motivation, warum du das so machst, wie du es machst, bislang nicht.

Warum hängst Du Dich so an dem Beispielcode auf?

Weil das das einzige ist, was ich von deinem Code kenne bislang. Und du zu diesem Beispielcode, insbesondere im Hinblick auf das Exception-Handling, um Rat gefragt hast.

Bedenke: Dir selbst ist dein gesamter Code, die gesamte Vorgehensweise, natürlich sonnenklar. Du kennst ihn ja schließlich seit mehreren Wochen. Ich kenne nur deinen Beispielcode und dein geschildertes Problem.

In annähernd jedem catch steht dann der Code:



> catch (Exception $e) {
> 
>   $this->ExceptionHandler($e);
>   	
> }

Damit landen also alle Exceptions beim eigenen ExceptionHandler().

Das bedeutet, dass du es für möglich erachtest, dass sich beim Aufrufen deines Skriptes mehrere Exceptions nacheinander ansammeln. Ist das sinnvoll? Tritt sowas in der Realität tatsächlich auf?

Vermutlich wirfst du an zuvielen Stellen Exceptions.

Dort soll dann jeder, der später mit den Klassen arbeitet, machen, was er möchte. Ich möchte, dass sie dann an das $Site - Object gegeben werden, was ich momentan mit Global $Site, etc.... mache.

Die Frage ist halt, was man im Problemfall an einer so späten Stelle mit den Exceptions noch anfangen kann. Ich würde sagen: Außer die gesammelten Exceptions als Fehlermeldungen auszugeben und dem User etwas anzuzeigen, an deren Problembehebung er nicht mithelfen kann (wenn z.B. der DB-Server unerreichbar ist, kann kein Webbrowser der Welt daran was ändern).

Gewiß: Die Exception ist für dich als Entwickler natürlich interessant. Aber für das Logging gibt es zahlreiche fertige Lösungen, zu deren Einsatz ich raten würde, beispielsweise "Log4PHP". Ein vernünftiges Logging-Konzept kann sehr viel zu einem wartbaren und auch gut debuggbaren Code beitragen.

Letztendlich hängt hier eben alles an der Frage: Was soll deine Softwarelösung dir bringen, und wärst du bereit, deine Voreingenommenheit zugunsten deiner aktuellen Lösung zu überwinden?

Die Frage, die ich gestern bestimmt mißverständlich oder schlecht formuliert habe -

Ja hast Du, häng' den Programmierjob an den Nagel, Junge!

Wie gesagt: Mir Zitate unterzuschieben, die ich nicht geschrieben habe, ist schlechter Stil.

... ja danke, ich weiß - könnte vielleicht besser lauten: Wie kann ich aus einer Klasse heraus die Eigenschaft einer anderen Klasse verändern (Ansätze mit extend würde hier glaube ich nicht so gut passen). Wie kann ich die Exceptions einer Klasse an eine andere delegieren. So, klappt es ja bereits. Global verwende ich tatsächlich nur in diesen ExceptionHandler()'n, sozusagen ganz provokativ.

Die Gestaltung von Exception-Klassen und deren Vererbung ist ein gar nicht mal simples Thema. Im Gegensatz zu anderen Klassen müssen alle Exceptions, die man selbst gestaltet, von der in PHP integrierten Klasse "Exception" erben.

Es gibt Leute, die hiervon ausgehend Exception-Typen basierend auf der Art des aufgetretenen Problems werfen, beispielsweise die InvalidArgumentException für fehlerhafte Parameter (als Kind der "LogicException"), und die UnexpectedValueException für das Auftreten unerwarteter Werte (als Kind der "RuntimeException"). Dieser Ansatz versucht, die auftretenden Probleme nach Typen zu gruppieren (die genannten Exceptions sind in PHP über die Standard PHP Library "SPL" verfügbar), so dass man in der Lage ist, je nach Fehlertyp die eine oder andere Fehlerbehebung zu versuchen, oder dem User eine entsprechende Fehlermeldung zu übermitteln.

Mein persönlicher Favorit hingegen ist, dass Exceptions nach den Klassen gruppiert vererbt werden, die thematisch als Module in einer Software auftreten: Eine Datenbankklasse "Db" wirft eine Db_Exception. Das darin enthaltene Modul Db_Connector wirft eine Db_Connector_Exception. Der Db_Connector_Mysql wirft eine Db_Connector_Mysql_Exception. Und die Exceptions erben in einer Kette jeweils voneinander. Damit wäre man in der Lage, auf einer sehr hohen Programmebene beim Aufruf der Methoden der Klasse "Db" mit try/catch alle Db_Exceptions zu fangen (man weiß dann, dass irgendwas in der Datenbankabfrage schief gelaufen ist und man kein Ergebnis bekommt), inklusive aller Db_Connector_Exception (weil z.B. der konfigurierte Datenbanktreiber nicht geladen werden konnte) und aller Db_Connector_Mysql_Exception (z.B. für fehlerhafte SQL-Querys).

Innerhalb der Db-Klasse würde man mit try/catch alle Db_Connector_Exception fangen können, in Db_Connector wären das Db_Connector_Mysql_Exception, etc... Wobei das Fangen nur Sinn ergibt, wenn man ein passendes Alternativprogramm anbieten kann.

Alles, was du glaubst tun zu müssen, um mit Exceptions in deinem Verständnis umzugehen, ist vermutlich falsch, weil du es nicht besser kennst - aber da werden wir sicher diverse Hinweise liefern, um das zu ändern.

Vielen Dank. Ich hoffe/glaube ich verwende es nicht völlig falsch. Ich habe zum Beispiel häufig im Constructor ein try-Block, der sich um mehrere Funktionen spannt, in denen ich dann exceptions werfe, die sich dann bis zum Constructor durchschlagen.

Hast du ein Codebeispiel 1:1? Denn sowas ist mit hoher Wahrscheinlichkeit ein Mißbrauch von Exceptions zur Programmsteuerung, und sollte vermieden werden.

An deinem Code ist, wie du dir denken kannst, einiges grausam schlecht designt. Die Tatsache, dass die Site ihren DB-Handler selbst instanziiert, ist problematisch; würde ich nicht so machen, sondern als Parameter dem Konstruktor übergeben, also außerhalb der Site in dem Code, der Site ins Leben ruft.

Würde ich nicht, da es mehrere Instantiierungen sind (würden dann zu viele Parameter). Außerdem finde ich Parameter zu "statisch" in Bezug auf eine Veränderung des Projektes.

Wenn du einer Klasse mehr als vier, fünf Parameter im Konstruktor mitteilen musst, hast du auch wieder ein Designproblem. Deine Klasse muss dann zuviel unter einen Hut bringen, muss zuviel parallel kennen und können.

Ich kann dir schwerlich was raten, wenn ich nicht weiß, was du warum gemacht hast. Wirfst du Exceptions? Wo? Und was soll das Ergebnis sein, wenn eine geworfen wird.

Ich werfe sie (bspw. in Methoden der Klasse DB) und sie landen beim ExceptionHandler der Klasse DB. Im Ende sollen sie zur Klasse Site, da ich sie dort aus der Eigenschaft Exception (Array) auslese und darstelle.

Es geht also ausschließlich um die Darstellung der Exceptions.

Wieviele Exceptions erwartest du pro Skriptaufruf? Wieviele Exceptions sind Folgefehler vorher aufgetretener Exceptions?

Nur mal so als Gedankenspiel: Wenn deine Datenbank down ist, wird vermutlich jeder Zugriff eine Exception werfen. Am Ende hast du also vermutlich zehn bis hundert Exceptions gesammelt, die dir alle nur die eine Information geben: Deine Datenbank ist unerreichbar. Inwiefern macht es Sinn, bei einem so handfesten Fehler wie einer unerreichbaren Datenbank überhaupt noch den Versuch zu unternehmen, irgendwie weiterzumachen?

Auf der anderen Seite: Wenn die Abfrage der Datenbank absolut optional ist, weil damit nur irgendein dekoratives Widget in die Seite eingebaut wird, warum ist das Werfen der Exception dann überhaupt für die Seitendarstellung relevant? DB-Abfrage versuchen, eventuelle Exception fangen, das definierte Ersatzergebnis zurückgeben (leeres Array, NULL, Leerstring), und an der Stelle, wo es zur Ausgabe kommen soll, wird dieses Ergebnis zu irgendeiner alternativen Reaktion führen: Entweder wird garnichts angezeigt, oder ein Text wie "Keine Ergebnisse gefunden" oder "Datenbank im Moment nicht erreichbar".

[...]