Tobias Hahner: wie __destruct sinnvoll nutzen?

Beitrag lesen

Hallihallo!

Danke für die Klarstellung. Damit ergibt sich ein anderes Bild, das durch die Verkürzung so nicht zu sehen war.

Gerne doch, das war auch so beabsichtigt :-)
(Ich hätte auch nicht erwartet, dass es zu einer Diskussion in dieser Richtung kommen könnte, da es meiner Meinung nach nur um die Frage "gibt es Anwendungsfälle, in denen man einen Destruktor brauchen kann?" ging.)

Was mich aber wundert, warum erst am Script-Ende feststeht, wohin diese Ausgabe geschickt werden soll.

Das ist so nicht die ganze Wahrheit. Natürlich weiss ich von Anfang an, wohin die Ausgabe gehen soll, zumindest in den meisten Fällen. Dazu etwas mehr weiter unten...

Wenn du den Text neben der Dateiausgabe am Ende auch noch ausgeben willst, sollte doch spätestens die allgemeine Programmlogik wissen, dass das notwendig ist und es von sich aus tun, kurz bevor sie selbst fertig ist und zum Aufrufer zurückkehrt. Du überträgst mit deiner Methode dem Destruktor Aufgaben, die nicht direkt ersichtlich sind.

Da laufen wir jetzt gerade Gefahr, aneinander vorbeizureden. Ist aber zum grössten mein Fehler, den ich weiter unten hoffentlich beheben kann.

Wenn man das Programm nicht kennt und nach der Ursache der Ausgabe sucht, findet man das nicht im normalen Programmablauf, sondern in einem implizit aufgerufenen Teil. Ist das gute Architektur?

Das will ich hoffen :-) Aber da ich Dich in diesem Forum für den Spezialisten in Sachen PHP halte, komme ich jetzt einfach mal zu dem "weiter unten", damit das Ganze hoffentlich zu einem stimmigen Gesamtbild kommt:

Meine log-Klasse ist ein kleiner Teil eines relativ grossen Konstrukts, das über die letzten 3 Jahre immer weiter gewachsen ist. Teilweise fing das Alles geschwürartig an zu wuchern, es gab immer mehr "Frickel"- Ecken, und deshalb bin ich seit ca. 2 Wochen dabei, Alles von Grund auf neu zu bauen.
Das "Konstrukt" ist im Prinzip nicht mehr und nicht weniger als eine Oberfläche für MySQL- Tabellen, plus einen "Arsch voll" Geschäftslogik. Es handelt sich momentan hauptsächlich um eine Lagerverwaltung mit angeschlossener Projektverwaltung, Tourenplanung, Zeiterfassung und Fakturierung usw.. Im Lager sind Artikel gelistet, die dann von der Projektleitung zu Packlisten "zusammengeklickt" werden können. Für die Dauer des Projekts werden dann die betreffenden Artikel (Mietgegenstände) aus dem Bestand gerechnet, die Mietpreise werden in Abhängigkeit zur Projektdauer in die Rechnung gerechnet, dazu dann noch die letzendlich geleisteten Arbeitsstunden, uswusf.
Dabei musste ich auch von Anfang an verschiedene Benutzer und Benutzergruppen verwalten können, weil z.B. die Leute im Lager die hinterher gestellten Rechnungen nicht angehen, usw.
Soweit die Vorgeschichte...

Damit das Alles auch zukünftig erweiterbar und überhaupt wartbar ist, habe ich Alles in "Module" verpackt (quasi Plugins), was eigentlich das Schwierigste dabei war.
Ausserdem kam jetzt vor Kurzem auch erst die Möglichkeit der Fernsteuerung per E-Mail hinzu. (Einige von uns haben Android Smartphones mit entsprechenden TimeTracker Apps, welche csv- Dateien versenden können. Diese werden dann von dem "Programm" empfangen, geparst und in MySQL übertragen, um nur ein Beispiel zu nennen)
Es gab also plötzlich einen völlig neuen Kontext, ich nenne ihn intern einfach "mail", der vorher nicht vorgesehen war.

Nun stand ich vor dem Problem des Testens und des Protokollierens von Fehlern oder Debug-Meldungen. Dabei ist es mir wichtig gewesen, auch jederzeit Kontrolle darüber zu haben, welche Meldungen überhaupt für wen interessant sind. Darüberhinaus brauchte ich auch noch die Möglichkeit, verschiedene Logs aus verschiedenen Modulen ein- und auszublenden, und und und...

Es gibt also zum Beispiel folgendes Szenario:

  • Nutzer schickt eine csv- Datei mit seinen Arbeitsstunden an den Server
  • Programm "main" wird per cron gestartet und merkt, dass da eine neue Mail wartet.
  • Programm "main" legt ein log an und sagt "INFO: neue Mail entdeckt am xxxxxxx um xx:xx"
  • Programm reicht die E-Mail weiter an Klasse "email"
  • Klasse "email" legt ein log an und sagt "INFO: habe hier eine neue Email, ich parse die dann mal eben"
  • Klasse "email" meldet diverse "DEBUG"- Meldungen an sein log, bzgl. MessageId, Subject, From usw.
  • Klasse "email" sagt "DEBUG: fertig, email ist geparst"
  • Programm "main" fragt Klasse "email" nach einigen Header- Zeilen und fragt Klasse "user", ob man daraus einen user basteln kann
  • Klasse "user" legt ein log an und sagt "INFO: ich wurde nach einem user-Objekt gefragt. Folgende FROM und RECEIVED hab ich"
  • Klasse "user" stellet fest, dass da Alles klar geht (natürlich inklusive diverser DEBUG- Meldungen) und spuckt ein user Objekt aus
  • Programm "main" sagt: "INFO: Yupp, User ist angemeldet mit der ID xxx"
  • Programm "main" gibt die Email weiter an "executor"
  • Klasse "executor" legt ein log an und sagt "INFO: executor gestartet. Ich acker mich jetzt durch die Mail mit der MessageId xxxxx"
  • Klasse "executor" fräst sich also durch die Email und findet irgendwo einen Befehl.
  • Klasse "executor" sagt also "INFO: Befehl gefunden in Zeile soundso: blabla(foo);"
  • Klasse "executor" fragt Klasse "user", ob der User die Berechtigung für diesen Befehl hat
  • Klasse "user" sagt "WARNING: Darf der nicht!" und gibt false zurück...
  • Klasse "executor" sagt "INFO: Befehl wird nicht ausgeführt"

Das war jetzt nur ein Beispiel...
Jetzt möchte ich natürlich dem User nur die Mitteilung geben, dass Alles geklappt hat, oder eben nicht.
Ich suche mir also im Programm "main" im Fehlerfall alle Meldungen die "WARNING" oder "ERROR" sind (muss ja nicht unbedingt an Berechtigungen scheitern) und schicke sie ihm. Aber nur die, die vom "executor" oder der angeforderten Routine stammen...
Wenn es aber einen echten Fehler, oder z.B. einen Einbruchsversuch mittels gefälschter Emails gegeben hat, bekomme ich als admin eine Mail, in der ALLE Meldungen aus ALLEN Klassen, auch die DEBUG enthalten sind.

Ich kann also effektiv unterscheiden, welche Meldungen aus welchem Programmteil ich sehen will. Unabhängig davon, auch wenn ich sie nicht sehen will, lasse ich sie, nach Klassen sortiert, aber auch abspeichern, damit ich Nicht-offensichtliche Fehler (z.B. Start- und Endzeit vertauscht, weil Jemand einen anderen TimeTracker benutzt, oder sonstwas) anhand der Debug- Meldungen nachvollziehen kann.

Die programmatische Nutzung ist dabei absichtlich extrem simpel gehalten:
Jede Klasse hat ihr eigenes Log- Objekt, das sie mit sich rumschleppt:
protected $log = logs::getInstance("Zeiterfassung", 'ALL|~DEBUG'); // zum Beispiel. DEBUG werden zwar gemerkt für Ausgabe, erscheinen aber nicht in der Datei
und im weiteren Verlauf der Klasse sind dann die Meldungen verteilt wie Kontrollausgaben:
$this->log->debug('verarbeite jetzt Zeile Nr. xxx');
oder
$this->log->warning('Nutzer hat nicht die Berechtigung!');
oder aber auch
$this->log->error('Das jetzt aber blöd gelaufen! DB ist putt!');

Zum Schluss kann ich von egal wo (normalerweise aus "main") die Einträge rausfischen, die ich anzeigen will:

$entries = logs::getMessages("Zeiterfassung","WARNING"); // gibt mir nur die WARNINGS aus Zeiterfassung
oder
$entries = logs::getMessages('','ALL');  // gibt mir Alles...

Um das Speichern muss ich mich nicht explizit kümmern. Will ich auch nicht. Ich bin faul, das macht der Destruktor des jeweiligen log-Objekts für mich. Wichtig ist mir vor Allem, dass ich hinterher mit den gefilterten Meldungen noch etwas anfangen kann. Dafür brauche ich das wachsende Array...

Ich hoffe, damit konnte ich etwas Licht in das Dunkel bringen, nachdem ich vorher so brutal das Rollo runtergelassen und die Lampe ausgeknipst habe...

Beste Grüsse,
    Tobias Hahner