mark: PHP - OOP - Wie sollte ich am besten meinen Code strukturieren?

Guten Abend,

ich bin neu, was Objektorientierte Programmierung angeht. Leider helfen mir die Tutorials mit Tieren, die Laute von sich geben nicht mehr weiter ;), bzw. bin ich zu doof dieses Konzept auf meinen Anwendungsfall zu übertragen. Mir geht es im Wesentlichen darum, wie ich meinen Code bestmöglich strukturieren kann und vor allem die Denkweise, die dahinter steckt.

##Mein Anwendungsfall: Ich bastle an einer Weboberfläche für einen Changelog.

Als Datengrundlage dienen XML-Dateien, welche das Hauptrelease der Software und die dazugehörigen "Subreleases" samt Kommentaren zu den jeweiligen Bugfixes und neuen Features enthält. Das Ganze ist so ausgelegt, dass man die Changelogs mehrerer Anwendungen damit abbilden kann.

Ich habe dazu eine Klasse (meine einzige Klasse) mit dem Namen "changelog" erstellt. Doch diese platzt so langsam aus allen Nähten und wird unübersichtlich und errinnert mich eigentlich stark an meinen Spaghetti-Code, den ich eigentlich überwinden wollte.

Die Methoden, die darin enthalten sind kann man wie folgt gliedern:

  • Getter und Setter: für die Konfiguration der Anwendung
  • Methoden zum Lesen und Mergen der einzelnen XML-Dokumente
  • Methoden zum Sortieren der einzelnen Einträge nach Release-Datum und Anwendung
  • Methoden zum Filtern nach Anwendungs-Name, Release-Datum und Version
  • Methoden für die View, mit denen ich übersichtliche Arrays erzeuge, die ich dann in der View mittels foreach durchschleife

Ich binde den Code, der sich in einer einzigen Datei befindet mit einer include Anweisung ein.

##Meine kläglichen Versuche Ich hab mir überlegt die Klasse in mehrere Dateien, gemäß der Typologie ihrer Methoden (Sortieren, Filtern, View, ...) aufzuspalten. Das ist jedoch nach kurzer Recherche unter PHP nur begrenzt möglich. Dann habe ich mich in Namespaces eingelesen. Doch auch das schien mir nicht der Schlüssel zur Lösung zu sein. Vererbung wüsste ich auch nicht, wie ich das in diesem Zusammenhang verwenden könnte.

##Meine Frage Meine Frage deshalb: WIE kann ich mein Projekt besser, d.h., besser objektorientiert strukturieren?

lg

mark

P.S.: Es ist ein kleines Projekt und ich möchte damit keine Diskussion über die Sinnhaftigkeit OOP in diesem Zusammenhang anstoßen. Mir geht es nur darum, das besser zu begreifen.

  1. Ich probiere jetzt mal ein Interface-Pattern aus.

    Sieht laut meinem bescheidenen Wissensstand so aus, als könnte es so funktionieren. Ich implementiere einfach jede Methodengruppe (Daten lesen, sortieren, filtern) als Interface und lege den entsprechenden Code in eine eigene Datei ab.

    Was meint Ihr?

    1. Da hast du eine gute Quelle gefunden. Implementierungen hinter Interfaces zu verstecken ist in PHP nicht ganz so wichtig, weil PHP nicht statisch gebunden ist. Interfaces brauchst Du in statisch gebundenen Sprachen, um hinter einem Interface unterschiedliche Implementierungen haben zu können (Polymorphie). Das lohnt sich dann, wenn man Unit Tests schreibt und einer Klasse X, die andere Klassen als Arbeitstiere nutzt, nur Simulanten unterschieben will. Testbarkeit ist allerdings nochmal ein ganz anderes Thema - befasse dich nicht mit zu viel auf einmal, sonst flippst Du aus :)

      Rolf

      1. Hallo Rolf,

        vielen lieben DANK für deine Hilfe. Bevor ich ausflippe werde ich jetzt erst mal deinen längeren Text versuchen zu verdauen :) Danke nochmals. Das ist ja schon ein ganzer Kosmos.

        Das Beispiel das ich verlinkt habe, hat mir deshalb so gut gefallen, weil es mir einen Weg zeigt, wie ich Klassen "orchestrieren", sprich die einzelnen Komponenten zusammenfügen kann. Ich glaube dahingehend habe ich große Defizite. Ich weiß noch nicht, wie ich Klassen "orchestriere" und zwischen ihnen Variablen in OOP-Manier austausche. Aber wie gesagt: ich verdaue das erst mal. Zum Glück ist bald Wochenende :)

        1. "Kosmos" ist genau richtig. Wenn Du mit OOP anfängst und dann Code von Leuten siehst, die das schon länger treiben - sehr viel länger - dann glaubst Du, die kommen vom anderen Stern :)

          Aber irgendwann hast Du deinen eigenen Orbitalaufzug gefunden und hebst selbst ab.

          Ich habe gerade mal nach Literatur gestöbert - der deutsche Kram scheint sehr alt zu sein (oder ich habe die neuen Sachen nicht gefunden). Wenn Du keine Angst vor Englisch hast: Vielversprechend finde ich die Bücher von Matt Zandstra (PHP Objects, Patters and Practices) und Josh Lockhart (Modern PHP - das Buch mit dem Marabu). Was von David Sklars Leguan (PHP Cookbook) zu halten ist, weiß ich nicht; in Cookbooks findet man viele fertige Lösungsvorschläge, muss aber immer den Salzstreuer bereithalten um die für's eigene Gericht passende Würze zu finden.

          Ich kenne diese Bücher aber allesamt selbst nicht, ich habe mich an den Bewertungen orientiert.

          Rolf

          1. "Kosmos" ist genau richtig. Wenn Du mit OOP anfängst und dann Code von Leuten siehst, die das schon länger treiben - sehr viel länger - dann glaubst Du, die kommen vom anderen Stern 😀

            Das ist ja völliger Unsinn denn der Sinn von OOP ist ja gerade der, einen Code zu schreiben, den ein Irdischer versteht.

            Aber Du hast natürlich Recht, im wahren Leben siehts tatsächlich oft so aus.

            MfG

            1. Und du verstehst mich nicht.

              Es geht nicht um schlecht aufgebauten Code, sondern darum, dass die Sprachmuster, die ein OOP-Experte einsetzt, von einem Anfänger typischerweise nicht durchschaut werden.

              Wo der Experte sagt: Klar, ist ein Visitor, ein Decorator, ein Command, und dort ist meine Dependency Injection Library, und dann vielleicht noch eine Bibliotheken hinzukommt, die "Konvention vor Konfiguration" betreibt und per Reflection die Zusammenhänge zwischen den Klassen erahnen - dann ist der Anfänger reif für die Klapse.

              Rolf

              1. Doch ich verstehe das schon wie Du das meinst. Und ich habe es oft genug erlebt und das Ganze noch gepaart mit einer grenzenlosen Arroganz.

                Es geht nicht um schlecht aufgebauten Code, sondern darum, dass die Sprachmuster, die ein OOP-Experte einsetzt, von einem Anfänger typischerweise nicht durchschaut werden.

                Das ist auch schlecht.

                Wo der Experte sagt: Klar, ist ein Visitor, ein Decorator, ein Command, und dort ist meine Dependency Injection Library, ...

                Das kann man alles lernen. Die Arroganz da draußen besteht jedoch gerade und sehr oft darin, dem neuen Kollegen zu suggerieren dass er das nie begreifen wird.

                MfG

  2. Sinnhaftig ist das Ganze durchaus. Ich versuche mal ein paar Hinweise, wie du herangehen könntest.

    Du hast XML Dokumente, schreibst Du. Wenn Du Dir das Schema dieser Dokumente ansiehst, wirst Du feststellen, dass dein "ChangeLog" ein Container ist. Er enthält ein "HauptRelease", dazu gibt es "SubReleases", ein Subrelease dokumentiert "Bugfixe". Das sind schonmal gleich 4 Klassen, ohne lange nachzudenken.

    Diese Klassen beschreiben aber NUR das Datengebilde zu einer Changelog XML Datei. Für deine Anwendung hast Du eine Klasse "Configuration", wo alle Konfigurationseinstellungen drinstecken. Du hast auch eine Klasse "Application" (die ich auch gern "Context" nenne), die sozusagen die Wurzel allen Übels darstellt. Dieses Objekt stellst Du über eine globale Variable bereit. Sie hat bspw. ein Property, unter dem Du das Configuration Objekt findest. Sie könnte auch ein Property "Log" haben, unter dem Du den gerade geladenen Changelog findest.

    Um eine Changelog-Datei zu laden, schreibst du eine weitere Klasse: ChangelogReader. Die hat eine öffentliche Methode "Load" und diese produziert aus einer XML Datei ein Changelog-Objekt mit allen Unterobjekten. Gerne kannst Du ihr weitere Klassen beigesellen wie einen HauptreleaseReader oder SubreleaseReader, die sich ausschließlich mit diesem Teil des XML Schemas befassen. Hängt davon ab, wie komplex dein Schema ist, ob sich das lohnt.

    Unabhängig von dem, was deine Seite alles kann, musst Du zumindest mal einen Changelog darstellen können. Falls Du ein Template-System für PHP nutzt, musst Du es füttern. Tust Du das nicht, musst Du das entsprechende HTML erzeugen. Aber egal, wie du es tust, du brauchst eine Klasse ChangelogRenderer, die sich um diese Aufgabe kümmert. Da ein Changelog Unterobjekte hat, schadet es nicht, die Darstellung eines Hauptreleases, eines Subreleases oder eines Bugfixes in einen HauptreleaseRenderer, SubreleaseRenderer und BugfixRenderer zu verteilen.

    Grundidee: Eine PHP Datei enthält genau eine Klasse. Jede Klasse kümmert sich um eine kleine, klar umrissene Aufgabe. Eine Methode darin tut genau eine Sache. Methoden mit mehr als 50 Zeilen können gelegentlich sinnvoll sein, sind aber meistens schon zu lang. Es sei denn, sie bestehen hauptsächlich aus Kommentar oder dein Schreibstil ist sehr zeilenfressend. Klassen mit mehr als 500 Zeilen bündeln meistens zu viele Teilaufgaben. Allerdings kann Dir eine zu starke Zerlegung in PHP Performanceprobleme bescheren, weil dann zu oft der Classloader anspringt und die Anwendung sich auf zu viele Dateien verteilt.

    Diese ganzen kleinen Klassen müssen dann vom Hauptprogramm geeignet erzeugt und zusammengesteckt werden, damit das Orchester sauber spielt. Wenn Du eine Seite zur Anzeige eines Changelogs hast, wirst Du einen für alle Seiten einheitlichen Teil haben, der Configuration und ein Application-Objekt erzeugt (z.B. in einer Funktion abgebildet, die in irgendeinem include steckt). Und dann solltest Du zügig dazu übergehen, das Steuerobjekt für die aufgerufene Seite zu erzeugen (das bekommt z.B. das Application-Objekt als Parameter) und darauf nur noch eine Run() Methode aufzurufen. Darin kommen dann die Unterscheidungen, ob es ein GET oder POST war, ob es Query-Parameter gab und wie zu reagieren ist, und wenn dann klar ist, was GENAU für diesen Request zu tun ist, erzeugst Du ein Worker-Objekt für genau diese Aufgabe und startest dieses.

    Das heißt: Du hast mehrere Kategorien von Objekten.

    • Rahmen (Application, Configuration)
    • Modell (ConfigLog, HauptRelease, SubRelease, BugFix, ...)
    • View (die Renderer-Klassen)
    • Controller (die Steuerklassen, die das Modell aufbauen und einen View dazu erzeugen)

    MVC. Model-View-Controller. Ein Konzept, dass recht bekannt ist und dass du gerne recherchieren kannst.

    Sauberes OOP ist viel Erfahrung, viel Kennenlernen von Code anderer Leute, viel Fehlermachen und daraus lernen, viel Lektüre über "gute" OOP Muster.

    Damit du vor lauter Klassen nicht wahnsinnig wirst, brauchst Du dann noch zweierlei:

    • eine IDE, mit der Du den Überblick behältst (ich mag die PHP Edition von Netbeans)
    • einen automatischen Classloader, der die benötigten Klassen in dem Moment nachlädt wo sie angesprochen werden. Da gibt's einiges in der wilden weiten Welt. Meiner ist selbstgeschrieben, das ist keine Raketenwissenschaft, aber die fertigen können natürlich mehr und du musst sie nicht testen. Nur bedienen lernen ;)

    Rolf

    1. Tach!

      Grundidee: Eine PHP Datei enthält genau eine Klasse. Jede Klasse kümmert sich um eine kleine, klar umrissene Aufgabe. Eine Methode darin tut genau eine Sache.

      Das ist schon der Kern einer guten Strukturierung. Dazu gibt es ein paar Faustregeln, zum Beispiel die: Wenn in der Beschreibung, was eine Codeeinheit tut, ein "und" vorkommt, dann macht sie zu viel.

      Die Sache mit den Tieren ist auch ein guter Aufhänger. Eine Kuh macht zwar Muh, und das kann eine ihrer Methoden sein, aber Wenn sie Mühe macht, ist das nicht mehr ihre Angelegenheit. Denn darum muss sich der Bauer kümmern. Melken wäre also keine Methode der Kuh, sondern eine vom Bauern. Und so weiter und so fort. Anders ausgedrückt geht es bei der Strukturierung um die Beantwortung der Frage, was wessen Aufgabe ist.

      dedlfix.

      1. @@dedlfix

        Die Sache mit den Tieren ist auch ein guter Aufhänger. Eine Kuh macht zwar Muh, und das kann eine ihrer Methoden sein, aber Wenn sie Mühe macht, ist das nicht mehr ihre Angelegenheit. Denn darum muss sich der Bauer kümmern. Melken wäre also keine Methode der Kuh, sondern eine vom Bauern.

        „Wer kennt die Frauen, aber sein Vieh kennt er genauer? Der Bauer!“
        —Sebastian Krämer, Burleske (in den Kostproben zum Hören; @Christian Kruse)

        LLAP 🖖

        --
        “When UX doesn’t consider all users, shouldn’t it be known as ‘Some User Experience’ or... SUX? #a11y” —Billy Gregory
        1. Hallo Gunnar,

          „Wer kennt die Frauen, aber sein Vieh kennt er genauer? Der Bauer!“
          —Sebastian Krämer, Burleske (in den Kostproben zum Hören; @Christian Kruse)

          Braucht leider Flash und ist mir daher nicht zugänglich. Aber danke für den Hint 😀

          LG,
          CK

          1. @@Christian Kruse

            Braucht leider Flash und ist mir daher nicht zugänglich.

            Ich werd Sebastian bei Gelegenheit mal fragen, ob ich was für ihn tun kann. ;-) Aber ich seh ihn nicht mehr so oft wie früher.

            LLAP 🖖

            --
            “When UX doesn’t consider all users, shouldn’t it be known as ‘Some User Experience’ or... SUX? #a11y” —Billy Gregory
            1. Hallo Gunnar,

              Braucht leider Flash und ist mir daher nicht zugänglich.

              Ich werd Sebastian bei Gelegenheit mal fragen, ob ich was für ihn tun kann. ;-)

              Das täte der Seite sicherlich gut 😉

              LG,
              CK

    2. Hallo,

      Grundidee: Eine PHP Datei enthält genau eine Klasse. Jede Klasse kümmert sich um eine kleine, klar umrissene Aufgabe. Eine Methode darin tut genau eine Sache. Methoden mit mehr als 50 Zeilen können gelegentlich sinnvoll sein, sind aber meistens schon zu lang.

      Vielen Dank für Deine Ansätze. Du hast sehr gut mit Beispielen geschildert, wie die Aufteilung geschehen könnte. Da kann ich für meinen Anwendungsfall auch was mitnehmen (meine Klasse hat momentan >2k Zeilen :) Ich frage mich allerdings wie (oder unter welchem Schlagwort) ich mehr dazu herausfinden kann, wie die Klassen miteinander kommunizieren sollen. Ich könnte mir bestimmt was überlegen, aber ich bin ziemlich sicher, dass das dann wieder Käse ist. Wäre design patterns der richtige Begriff? Kannst Du dieses Buch empfehlen: https://www.oreilly.de/buecher/120235/-php-design-patterns.html. Oder kannst Du sogar kurz mit einem Codebeispiel darlegen, wie zum Beispiel Application mit Configuration und Log in Verbindung stehen?

      Vielen Dank.

      Cheers,
      BaBa

      --
      BaBa kommt von Basketball
      1. Tach!

        Ich frage mich allerdings wie (oder unter welchem Schlagwort) ich mehr dazu herausfinden kann, wie die Klassen miteinander kommunizieren sollen. Ich könnte mir bestimmt was überlegen, aber ich bin ziemlich sicher, dass das dann wieder Käse ist. Wäre design patterns der richtige Begriff?

        Das ist nur ein generischer Begriff. Auch schlechte Design-Patterns sind Design-Patterns. Was du suchst sind eher Prinzipien. Zum Beispiel Clean Code. Such mal nach Clean Code Talks von Miško Hevery. Das sind 5 Vorträge, auf Youtube zu finden.

        Kannst Du dieses Buch empfehlen: https://www.oreilly.de/buecher/120235/-php-design-patterns.html. Oder kannst Du sogar kurz mit einem Codebeispiel darlegen, wie zum Beispiel Application mit Configuration und Log in Verbindung stehen?

        Im Grunde läuft es drauf hinaus, dass du deinen Code so gestaltest, dass nicht unvorhergesehenes passiert und der Ablauf nachvollziehbar ist. Code soll Dinge übergeben bekommen, mit denen er was anstellen soll, statt einfach irgendwo nachzuschauen, was dann auch noch implizit erstmal bereitgestellt werden muss. Wenn die Abhängigkeiten hineingereicht werden, ist deutlich, was als Voraussetzungen geschaffen werden muss. Das Ergebnis kommt dann natürlich auch als Rückgabewert statt dass man es sich anderenorts holen muss.

        dedlfix.

        1. Such mal nach Clean Code Talks von Miško Hevery. Das sind 5 Vorträge, auf Youtube zu finden.

          Cool. Werde ich!

          Code soll Dinge übergeben bekommen, mit denen er was anstellen soll, statt einfach irgendwo nachzuschauen, was dann auch noch implizit erstmal bereitgestellt werden muss.

          Check! Dependency injection nehme ich mir bereits zu Herzen. Gibt es in diesem Stil noch mehr "good practices" die man vielleicht irgendwo mal vereint aufgeschrieben hat? Ich schau mal die talks. Danke!

          Cheers,
          BaBa

          --
          BaBa kommt von Basketball
          1. Tach!

            Dependency injection nehme ich mir bereits zu Herzen. Gibt es in diesem Stil noch mehr "good practices" die man vielleicht irgendwo mal vereint aufgeschrieben hat?

            SOLID

            dedlfix.

      2. Ja, genau damit (Kommunikation, Verbindungen zwischen Klassen, Scoping) hab ich auch so meine Schwierigkeiten. Ich bin froh, das ich damit nicht alleine bin :) Beispielsweise: Wann übergebe ich eine Variable, wann wird sie Teil eines globalen Objektes... Aber jetzt probier ich mal mit den vielen Tipps zurechtzukommen und dann wird schon was werden.

        1. Tach!

          Ja, genau damit (Kommunikation, Verbindungen zwischen Klassen, Scoping) hab ich auch so meine Schwierigkeiten. Ich bin froh, das ich damit nicht alleine bin :) Beispielsweise: Wann übergebe ich eine Variable, wann wird sie Teil eines globalen Objektes...

          Gar nicht. Du kannst Werte übergeben, die unter anderem in Variablen abgelegt sein können, aber keine Variablen. Das mag jetzt krümelkackerisch klingen, aber das ist, was wirklich abläuft, und diese Einsichten in die internen Dinge, helfen beim Verstehen von Systemen.

          Wann aber ein Wert wohin gehen muss, kommt natürlich auf die Anforderung an, wann er wo gebraucht wird. Globale Dinge sollte man vermeiden, wenn sie als "Müllhalde" verwendet werden, auf der man sich von überall aus bedient oder seinen Krams ablegt.

          dedlfix.

          1. Das mag jetzt krümelkackerisch klingen, aber das ist, was wirklich abläuft, und diese Einsichten in die internen Dinge, helfen beim Verstehen von Systemen.

            Ja, die Sprache bringt es an den Tag. Meine Meinung ist, wenn man etwas nicht erklären kann, hat man es auch nicht verstanden. Dafür braucht es genau definierte Begriffe. Und das mit Werten und Variablen ist ein gutes Beispiel. Danke dafür.

          2. Du kannst Werte übergeben, die unter anderem in Variablen abgelegt sein können, aber keine Variablen. Das mag jetzt krümelkackerisch klingen, [...]

            Darf ich das böse Wort call-by-reference zwischen die bröckeligen Fäkalien streuen?

            Rolf

            1. Tach!

              Du kannst Werte übergeben, die unter anderem in Variablen abgelegt sein können, aber keine Variablen.

              Darf ich das böse Wort call-by-reference zwischen die bröckeligen Fäkalien streuen?

              Ja, aber auch dann übergibst du keine Variablen, sondern eine Referenz auf irgendwas.

              $foo = einObjekt;
              $bar = einObjekt;
              $qux = $foo;
              

              Natürlich ist der Sprachgebrauch üblich, dass man $foo ein Objekt zugewiesen hat. Das vereinfacht die Kommunikation. Aber im Hinterkopf sollte man wissen, dass es doch etwas anders abläuft. Man kommt nämlich ins Straucheln, wenn man die zweite und dritte Zeile erklären möchte. Ist in $bar nun eine Kopie? Ist $qux eine Kopie oder zeigt $qux auf $foo? Mitnichten. Stattdessen liegt das Objekt irgendwo im Speicher und alle drei Variable referenzieren in gleicher Weise auf es. Genauerweise müsste man so formulieren. $foo bekommt eine Referenz auf einObjekt zugewiesen, $bar ebenfalls, und $qux bekommt eine Kopie der in $foo enthaltenen Referenz.

              Mit skalaren Werte wird intern auch nicht anders verfahren. Für einen Wert existiert ein Container (zval-Datenstruktur), in dem dieser nebst Metadaten (z.B. Typinformation) abgelegt ist. Eine Variable referenziert auf diesen Container. PHP arbeitet nach dem Prinzip copy-on-write. Wenn nun

              $a = 42;
              $b = a;
              

              ausgeführt wird, existiert weiterhin nur ein Container, aber $a und $b verweisen auf diesen. Erst wenn sich $b ändert, wird eine Kopie des Containers erzeugt, mit dem dann weitergearbeitet wird. Bei

              $a = 42;
              $b &= a;
              

              passiert auch nur ein ähnlicher Vorgang.

              Literatur dazu im PHP-Handbuch (verlinkt):

              dedlfix.

            2. Tach!

              Darf ich das böse Wort call-by-reference zwischen die bröckeligen Fäkalien streuen?

              Nebenbei: Jemand hatte deinen Beitrag als unkonstuktiv markiert, vermutlich wegen der Fäkalien. Ich habe mir erlaubt, dein Zitat zu erweitern, damit der Bezug klarer wird und hoffentlich deutlicher hervorgeht, dass es kein Affront sein soll.

              dedlfix.

              1. Danke für die Rückmeldung. Als Schreiber bekommt man die Meldung gar nicht mit.

                Da der Bezug direkt im Beitrag darüber stand, hatte ich das als unproblematisch angesehen. Aber wer die Threads nicht in der Baumansicht liest, mag das übersehen.

                Und natürlich WOLLTE ich Dich ein bisschen sticheln. Deine Aussage "man übergibt immer nur Werte" brauchte den nun losgetretenen Diskurs über den Unterschied zwischen call-by-value und call-by-reference und wann PHP was macht.

                Das "Copy on Write" ist eine wichtige und mir neue Info - ich hatte irgendwo gelesen, dass PHP seine "klassischen" Datentypen, also Skalare und das Array, als Wert übergibt, solange der Empfänger nicht einen Referenzparameter deklariert hat. Das Array ist für Leute, die aus der Sprachwelt der C-Abkömmlinge kommen, aber eine Anomalie, weil Arrays dort immer als Referenz übergeben werden. Um nicht ständig Array-Klone zu erzeugen, hatte ich mir in PHP angewöhnt, größere Arrays nur an Referenzparameter zu übergeben. Learned lesson: Solange die aufgerufene Funktion das Array nur liest, gibt es keinen Klon und damit keine Performance-Ohrfeige.

                So. Und nun ist Karneval. Alaaf!
                Rolf

  3. ist so ausgelegt, dass man die Changelogs mehrerer Anwendungen damit abbilden kann.

    Ich nehme mal an, dass sich hieraus immer wiederkehrende Methoden ergeben könnten.

    Ich habe dazu eine Klasse (meine einzige Klasse) mit dem Namen "changelog" erstellt.

    Hier sehe ich die Basisklasse, nennen wir sie Changelog. Die o.g. Anwendungen sind dann die Subklassen:

    Changelog::Foo, Changelog::Bar, Changelog::Boo usw. für die Abbildung der Changelogs dieser Anwendungen Foo, Bar, Boo usw.. Jetzt musst Du nur noch diese Klassen an URLs binden und fertig ist das Framework.

    Code organisieren ist nun ganz einfach, zum Qualifizieren von Methoden verwendest Du entweder Overload oder Attribute in den jeweiligen Instanzen.

    Wird schön übersichtlich, habs ganz genauso gemacht.

    MfG

  4. Als Datengrundlage dienen XML-Dateien, welche das Hauptrelease der Software und die dazugehörigen "Subreleases" samt Kommentaren zu den jeweiligen Bugfixes und neuen Features enthält. Das Ganze ist so ausgelegt, dass man die Changelogs mehrerer Anwendungen damit abbilden kann.

    Dass genau hier die neue Strukturierung (Aufteilung in Klassen) ansetzen sollte, haben wir ja gestern schon geschrieben. Und wenn Dir ein XML-Schema für eine bildliche Verinnerlichung komplexer Datenstrukturen wichtig ist, so ist das auch in Ordnung.

    Aber was OOP betrifft, so braucht eben die Instanz einen wahlfreien Zugriff auf diese komplexe Datentruktur. Und genau dafür hat eine Instanz der jeweiligen Klasse Eigenschaften, da liegt der ganze Changelog-Tree (trunk, branches...) drin.

    Die Ähnlichkeit zu SVN ist kein Zufall -- Ohne OOP ist sowas schwerlich zu machen.

    MfG

  5. Ich bin nun zur Tat geschritten und habe versucht eure Ratschläge in Code umzuwandeln. Ich hoffe ich überstrapaziere eure Geduld nicht, wenn ich hier meinen Code und meine Überlegungen poste und Euch um Feedback bitte. Ich habe erst wenige Zeilen Code geschrieben, doch ich glaube bereits da sieht man die grundlegenden Probleme, die ich mit der Strukturierung Objekt orientierter Programmierung habe.

    Ich bitte auch Rolf gleich schon im Voraus um Entschuldigung, sollte ich seine gut gemeinten Ratschläge völlig in den Sand gesetzt haben :)

    Also los! Hosen runter, auf gehts!

    #INTRO:

    Meine Entwicklungsumgebung

    • Editor: Visual Studio Code mit Plugins für PHP-Intellisense und XDebug
    • php 7.1.1 ich verwende den mitgelieferten Server php -S localhost:8000. Kein Apache o.ä.
    • composer habe ich zur Installation des language-Servers benötigt, so, dass ich Intellisense habe und auch für's autoloading der Klassen, was ich aber nicht hinbekommen habe.
    • npm als paketmanager für's Frontend

    Anmerkung: Ich möchte keine vollwertige IDE verwenden, da mich diese mMn, ähnlich wie ein Framework von der Erlernung der Basis abhält und ich "Magie" vermeiden möchte. Auch auf die Erlernung einer neuen IDE möchte ich zunächst verzichten.

    Meine Ordner- und Dateistruktur:

    • assets
    • config
      • config.json
    • data
      • anwendung_v1.0.xml
      • anwendung_v2.0.xml
      • beliebigerDateiname.xml
    • src
      • changelog
        • App.php
        • ChangelogReader.php
        • Config.php
        • Context.php
        • Language.php
        • SortData.php
        • TransformData.php
        • UrlParams.php
    • vendor
    • index.php
    • composer.json
    • composer.lock
    • package.json

    Ordner "assets": Hier befinden sich css, js, und img. Ich gehe nicht weiter darauf ein, weil irrelevant.

    Ordner "config": Hier befindet sich die Datei config.json, welche das Konfigurationsobjekt meiner Anwendung enthält.

    Ordner: "data": Hier befinden sich die ganzen XML-Dokumente. Pro Datei ein Release mit "Subreleases". Schema folgt weiter unten.

    Ordner "src\Changelog": hier befindet sich mein PHP-Quellcode. Auf die Klassen gehe ich weiter unten ein.

    Die restlichen Dateien brauche ich glaube ich nicht beschreiben. Ich hoffe meine Ordnerstruktur ist weitestgehend Standard.

    ANS EINGEMACHTE

    Workflow

    Mein Workflow ist im Moment sehr holprig. Ich teste die Ausgabe einer jeden Methode, indem ich die entsprechende Klasse in der index.php initialisiere und dann die Methode mit print_r ausgebe. Scheint mir recht Zeitaufwendig. Für Anregungen -und sei es auch nur ein Wink mit dem Zaunpfahl- wäre ich dankbar. Ich habe zwar einen Debugger, aber den nutze ich nicht wirklich, weil ich auch da erst mal die Methode aufrufen muss, damit ich sie debuggen kann. Da bin ich gleich schnell mit print_r.

    Meine Überlegungen zum Code

    Ich habe versucht mich an Rolfs Vorschläge zu halten. Manchmal ist das gelungen, oft haben sich dadurch erst neue Problemstellugen aufgetan, die ich nicht immer zufriedenstellend handeln konnte.

    Die erste Hürde für mich war, wo ich denn eigentlich beginnen sollte. Ich hab mir gedacht das Lesen und Parsen der Konfigurationsdatei könnte ein guter Einstieg sein. Ich habe mich hier für das Singleton-Pattern entschieden, aus dem vielleicht naiven Grund, dass dieses Pattern verhindert, dass ich eine Klasse mehrfach initialisiere, so, dass nur ein Objekt exisitiert und, weil in den Beschreibungen stand, dass das Singleton-Pattern dafür geeignet ist Objekte global zur Verfügung zu stellen. Ich habe natürlich auch gelesen, dass das Singleton-Pattern mit Vorsicht zu genießen ist, ja sogar vollkommen abzulehnen, weil es nur schwer testbar (TDD) ist und man es durch Dependency-Injection ersetzen sollte. Dependency-Injection kann ich noch nicht und TDD wollte ich mir nicht als zusätzliche Hürde auferlegen. Deshalb hier mein Code. Er findet sich unter src/Changelog/Config.php. Der Datei- und Klassennamen ist etwas ungünstig. Ich werde das in ConfigReader umbenennen. Zunächst aber das Konfigurationsobjekt:

    
    {
        "dataFolder": "data", 
        "iconTable": {
            "bugfix": "bug_report",
            "feature": "star"
        },
        "avaiableLang": [
            "de",
            "en",
            "fr",
            "it"
        ],
        "defaultLang": "de",
        "dateFormat": "d.m.Y",
        "translations": {
            "Application":{
                "de":"Anwendung",
                "it":"Applicazione",
                "fr":"Application"
            },
            "Language":{
                "de":"Sprache",
                "it":"Lingua",
                "fr":"Langue"
            },"All":{
                "de":"Alles",
                "it":"Tutto",
                "fr":"Tout"
            },
            "Jump to version":{
                "de":"Springe zu Version",
                "it":"Salta alla versione",
                "fr":"Aller à la version"
            },
            "Select application":{
                "de":"Anwendung auswählen",
                "it":"Scegliere applicazione",
                "fr":"Sélectionner application"
            },
            "Select language":{
                "de":"Sprache wählen",
                "it":"Scegli lingua",
                "fr":"Sélectionner Langue"
            }
        }
    }
    
    
    
    <?php
    namespace Changelog;
    
    class Config {
    
        public $data;
        private static $instance = null;
            
        private function __construct(){
            return $this->getConfigObject();
        }
    
        public static function getData() {     
            if(self::$instance == null) {
                self::$instance = new Config();
            }
                
            return self::$instance;
        }
    
        private function getConfigObject(){
            $configJson = file_get_contents($_SERVER['DOCUMENT_ROOT'] . "\config\config.json", false);
            $this->data = json_decode($configJson, true);
        }
    
        private function __clone() {
        }
         
        private function __wakeup() {
        }
    }
    

    Ich kenne MVC nur oberflächlich, aber ich weiß, dass da an erster Stelle ein Router steht, der die einkommende Request verarbeitet und damit dann einen Controller aufruft. Also habe ich gedacht, es wäre eine gute Idee mit der Umsetzung eben dieses Routers fortzufahren.

    Nur habe ich kein Routing im klassischen sinne, sondern nur 3 optionale Parameter, die mittels GET übergeben werden (Sprache "l", Version "v" und Anwendungs Name "a"). Die Seite die aufgerufen wird ist immer die selbe, nur eben mit anderen Queries. Wenn kein Query vorhanden ist gebe ich einen leeren String zurück. Ich verwende aus oben genannten Gründen wieder das Singleton-Pattern. Die Datei ist src/Changelog/UrlParams.php

    <?php
    namespace Changelog;
    
    
    class UrlParams {
        public $data;
        private static $instance = null;
            
        private function __construct(){
            return $this->getUrlParams();
        }
    
        public static function getData() {     
            if(self::$instance == null) {
                self::$instance = new UrlParams();
            }
                
            return self::$instance;
        }
    
        private function getGETParam($param){
            if( isset($_GET[$param]) ){
                return $_GET[$param];
            }else{
                return '';
            } 
        }
    
        private function getLanguage (){
            return $this->getGETParam('l');
        }
    
        private function getApplicationHash(){
            return $this->getGETParam('a');
        }
    
        private function getVersionHash(){
            return $this->getGETParam('v');
        }
    
        public function getRequestParams(){
            return $this->requestParams;
        }
    
        public function getUrlParams(){
            $this->data["language"] = $this->getLanguage();
            $this->data["apphash"] = $this->getApplicationHash();
            $this->data["versionHash"] = $this->getVersionHash();
        }
    
        private function __clone() {
        }
         
        private function __wakeup() {
        }
    }
    
    

    Dann habe ich mir den ChangelogReader vorgenommen. Dieser liest alle im Ordner "data" enthaltenen XML Dateien und kettet sie aneinander. Auch hier habe ich wieder das Singleton-Pattern verwendet. Da ich dieses Array ja global zur Verfügung stellen muss, damit ich es anschließend gemäß des Konfigurationsobjektes, und den GET-Parametern transformieren, filtern und sortieren kann.

    Hier wurde ich zum ersten mal stutzig, ob das mit dem Singleton-Pattern wirklich so eine gute Idee ist. Ich hatte und habe dafür aber keine Argumente dagegen, außer, dass es sich nicht gut anfühlt dieses Pattern so häufig zu verwenden. Deshalb nachfolgend: Singleton die zum 3. Mal.

    
    <?php
    namespace Changelog;
    
    use Changelog\Config;
    
    class ChangelogReader {
        public $data = [];
        private static $instance = null;
            
        private function __construct(){
            return $this->getFileContent();
        }
    
        public static function getData() {   
            if(self::$instance == null) {
                self::$instance = new ChangelogReader();
            }
                
            return self::$instance;
        }
    
        private function getFileContent(){
            $fileList = $this->getFileList();
            
            foreach($fileList as $filename) {
                $xmlFile = file_get_contents($filename, FILE_TEXT);
                $xml = simplexml_load_string($xmlFile);
    
                array_push($this->data, $xml);
            }
        }
    
        private function getFileList() {
            $files = glob( Config::getData()->data['dataFolder'] . "/*xml");
    
            if ($files == false || empty($files)) {
                die("NO FILES FOUND");
            }
    
            return $files;
        }
    
        private function __clone() {
        }
         
        private function __wakeup() {
        } 
    }  
    

    So. Nun hatte ich alle Informationen beisammen, um gemäß Rolfs Ratschlägen das Context-Objekt zu erstellen. In der nachfolgenden Klasse bündle ich alle Informationen aus UrlParams, Config und ChangelogReader. Von einem ChangelogWriter sehe ich vorerst der Einfachheit halber ab. Und wieder grüßt das Singleton-Pattern:

    
    <?php
    namespace Changelog;
    
    use Changelog\UrlParams;
    use Changelog\Config;
    use Changelog\ChangelogReader;
    
    class Context {
        public $context = [];
        private static $instance = null;
            
        private function __construct(){
            return $this->getContext();
        }
    
        public static function getData() {    
            if(self::$instance == null) {
                self::$instance = new Context();
            }
                
            return self::$instance;
        }
    
        private function getContext(){
            $this->context["urlParams"] = UrlParams::getData()->data;
            $this->context["config"] = Config::getData()->data;
            $this->context["data"] = ChangelogReader::getData()->data;
        }
    
        private function __clone() {
        }
         
        private function __wakeup() {
        } 
    } 
    

    Die Idee war nun, das mit dieser Klasse erstellte Context-Objekt an eine Klasse mit dem Namen "App" zu übergeben darin befindet sich eine Methode Worker, die die gesammelten XML-Daten gemäß der GET-Parameter und den Konfigurationseinstellungen abarbeitet.

    Hier habe ich nun, das Problem, dass ich nicht weiß, wie ich das Objektorientiert abbilden soll. Die Methode "Worker" würde ja dann nur statische Funktionen aufrufen und mein riesengroßes Context-Objekt als Parameter übergeben. Das riecht wieder nach Spaghetti-Code und ich habe da bislang noch keinen Ausweg gefunden. Erschwerdend kommt hinzu, dass ich nicht weiß, ob das, was ich bis jetzt gemacht habe überhaupt gut ist.

    Deshalb bitte ich um Euer Feedback zu der allgemeinen Vorgehens- und Denkweise, meinem Workflow, zu Variablen- und Klassennamen, zur Strukturierung des Projektes und vielleicht ein Tipp, wie ich weitermachen kann.

    Besten Dank.

    mark

    P.S.: In der index.php verwende ich folgende Codzeile, dass meine Klassen automatisch geladen werden. Mit dem vom Composer angebotenen autoloader habe ich das leider noch nicht hinbekommen.

    <?php
    namespace Changelog;
    
    spl_autoload_register(function ($class) {
        include 'src\\' . $class . '.php';
    });
    
    
    1. Hallo mark,

      74 Zeichen bis zum Limit 🙀

      Bis demnächst
      Matthias

      --
      Dieses Forum nutzt Markdown. Im Wiki erhalten Sie Hilfe bei der Formatierung Ihrer Beiträge.
      1. 😀 Ja, ich hab mir auch kurz überlegt, ob ich das Fass voll machen soll. Sonst hätte ich eben ein Bild vom restlichen text gepostet ;)

        Wie der Text des Mannes ...

        Die Kurzfassung: Ich habe 4x ein Singleton-Pattern verwendet um mir ein riesen Context-Objekt zusammenzubauen. Dieses habe ich nun vor zu filtern und sortieren, weiß aber nicht, wie ich das objektorientiert hinbekomme und, ob es nicht besser wäre das anders zu lösen. Hilfe!

    2. Tach!

      Anmerkung: Ich möchte keine vollwertige IDE verwenden, da mich diese mMn, ähnlich wie ein Framework von der Erlernung der Basis abhält und ich "Magie" vermeiden möchte. Auch auf die Erlernung einer neuen IDE möchte ich zunächst verzichten.

      Ich denke, da überschätzt du, was eine IDE zu tun in der Lage ist. Sie verdeckt nichts, wie das ein Framework tut, wenn es eine Schicht auf die nativen Funktionalitäten legt. Sie schreibt dir auch nicht auf magische Weise deinen Code. Sie hilft nur enorm, indem sie Abhängigkeiten findet, dich mit einem Tastendruck zur Definition einer Variable oder Funktion bringt, und dir alle Verwendungen von Dingen aufzeigen kann. Sie kann auch Tipps geben, wie man im Detail besseren Code schreiben kann. Auch der Lernaufwand ist nun soo groß auch wieder nicht. Man muss ja nicht alle Funktionen bis ins kleinste Detail kennen. Es reicht, wenn man sie wie zunächst wie einen Texteditor verwendet und dann nach und nach nebenbei die Funktionen kenenlernt.

          - SortData.php
          - TransformData.php
      

      Daten ist das, was man gemeinhin mit Computerprogrammen zu verarbeiten gedenkt. Versuch mal einen besseren Namen zufinden, der genauer bezeichnet, welche Daten da verarbeitet werden.

      Mein Workflow ist im Moment sehr holprig. Ich teste die Ausgabe einer jeden Methode, indem ich die entsprechende Klasse in der index.php initialisiere und dann die Methode mit print_r ausgebe. Scheint mir recht Zeitaufwendig.

      Testen ist zeitaufwendig, egal wie du es anstellst. Du kannst nur Zeit sparen, wenn du immer wieder dasselbe testen willst, dass du dir da Tests schreibst, die automatisch abgearbeitet werden können. Das verschlingt aber initial noch eine Menge Zeit mehr, aber du kannst dann sicher sein, dass zumindest für deine geschriebenen Testfälle sich die Komponenten korrekt verhalten. (TDD, hast du ja schon genannt.)

      Die erste Hürde für mich war, wo ich denn eigentlich beginnen sollte. Ich hab mir gedacht das Lesen und Parsen der Konfigurationsdatei könnte ein guter Einstieg sein. Ich habe mich hier für das Singleton-Pattern entschieden, aus dem vielleicht naiven Grund, dass dieses Pattern verhindert, dass ich eine Klasse mehrfach initialisiere, so, dass nur ein Objekt exisitiert und, weil in den Beschreibungen stand, dass das Singleton-Pattern dafür geeignet ist Objekte global zur Verfügung zu stellen.

      Wenn du ein Objekt instantiierst und dieses durchreichst, hast du auch nur ein Objekt.

      Dependency-Injection kann ich noch nicht und TDD wollte ich mir nicht als zusätzliche Hürde auferlegen.

      DI ist im Grunde genommen nur, dass man übergibt, womit die Funktionen und Objekte arbeiten sollen, statt dass sie sich die Daten selbst holen. Mehr nicht. Es gibt da noch DI-Frameworks, die beim Auflösen der Abhängigkeiten helfen, aber die kannst du für den Anfang auch unbeachtet lassen.

      Ich hab die Tage schon mal die Clean Code Talks von Miško Hevery (5 Videos, glaub ich sind das) empfohlen, der beschreibt auch ein paar Aspekte guter Code-Strukturierung.

      private function getConfigObject(){
          $configJson = file_get_contents($_SERVER['DOCUMENT_ROOT'] . "\config\config.json", false);
          $this->data = json_decode($configJson, true);
      }
      

      Das ist ein Teil, der das Prinzip "don't look for things" nicht einhält. Der Dateiname wäre ein Kandidat für DI. Bei einer Änderung muss man den nicht irgendwo im Code suchen, sondern hat ihn irgendwo zentral im Bereich der Anwendungsinitialisierung. Eine Konstante bietet sich sogar dafür an. Das ist auch der einzige Konfigurationswert, der literal notiert sein muss, die anderen stehen ja dann in der Datei.

      private function __clone() {
      }
       
      private function __wakeup() {
      }
      

      Lass weg, was du nicht brauchst. YAGNI.

      Ich kenne MVC nur oberflächlich, aber ich weiß, dass da an erster Stelle ein Router steht, der die einkommende Request verarbeitet und damit dann einen Controller aufruft. Also habe ich gedacht, es wäre eine gute Idee mit der Umsetzung eben dieses Routers fortzufahren.

      Der Router gehört nicht zum MVC-Muster. Gleichwohl findet man ihn in MVC-Frameworks, weil er eine wichtige Hilfsfunktion für solchen Anwendungen bereitstellt. An erster Stelle aber steht üblicherweise ein Front-Controller, der Initialisierungsarbeiten vornimmt und der dann den Request zum Router schickt, zwecks Ermittlung des zu verwendenden MVC-Controllers.

      Ich verwende aus oben genannten Gründen wieder das Singleton-Pattern [für den Router].

      Was ist so wichtig daran, dass es nur eine Routerinstanz geben darf? Wird die überhaupt mehrfach von Teilen der Anwendung angefragt? Vermutlich wohl eher nicht. Dass etwas nur einmal verwendet wird, ist zum Beispiel keine stichhaltige Begründung für ein Singleton (abgesehen von den generellen Argumenten gegen Singletons).

      private function __construct(){
          return $this->getUrlParams();
      }
      

      Konstruktoren haben keine Rückgabewerte. Es heißt immer, Konstruktoren erstellen Objekte, so dass man zu dem Schluss kommen kann, dann könne der Konstruktor ja auch mal was anderes erstellen. Aber __construct ist nicht der Konstruktor, sondern nur ein Teil. Es ist nur die Konstruktor-Funktion, eine vom Konstruktionsvorgang aufgerufene spezielle Funktion, in der der Programmierer seinen Code unterbringen kann. Sie wird aufgerufen, nachdem das Objekt grundlegend instantiiert wurde. Das, worauf $this zeigt, existiert bereits und wird ohne weiteres Zutun vom new-Operator zurückgegeben. - Du kannst diese Funktion weiterhin Konstruktor nennen, so wie das im Prinzip alle Programmierer machen, aber mit dem Wissen im Hintergrund, dass das nur eine nachgelagerte Funktion im Konstruktionsprozess ist.

      private function getLanguage (){
          return $this->getGETParam('l');
      }
      
      private function getApplicationHash(){
          return $this->getGETParam('a');
      }
      
      private function getVersionHash(){
          return $this->getGETParam('v');
      }
      

      Für meinen Geschmack sind diese drei Funktionen entbehrlich. Gut, sie haben einen sprechenden Bezeichner, der mehr aussagt, als wenn man nur die Zeile im Funktionskörper sieht. Aber an der einzigen Stelle, an der sie aufgerufen werden, geht bereits recht gut hervor, wofür das l, a und v steht. Overengineering muss nämlich auch nicht sein.

      Dann habe ich mir den ChangelogReader vorgenommen. Dieser liest alle im Ordner "data" enthaltenen XML Dateien und kettet sie aneinander. Auch hier habe ich wieder das Singleton-Pattern verwendet. Da ich dieses Array ja global zur Verfügung stellen muss, damit ich es anschließend gemäß des Konfigurationsobjektes, und den GET-Parametern transformieren, filtern und sortieren kann.

      Dieses "muss" halte ich für ein Gerücht. Wenn Verwender den Reader brauchen, dann kann man ihnen den auch übergeben. Sie müssen ihn sich nicht selbst holen. Und im Falle von TDD kann man dann auch eine zum Testen besser geeignete Instanz übergeben, anstatt dass man eine mehr oder weniger komplette Programmumgebung nachbauen muss, in der sich der Testkandidat wohl fühlt.

      Hier wurde ich zum ersten mal stutzig, ob das mit dem Singleton-Pattern wirklich so eine gute Idee ist. Ich hatte und habe dafür aber keine Argumente dagegen, außer, dass es sich nicht gut anfühlt dieses Pattern so häufig zu verwenden. Deshalb nachfolgend: Singleton die zum 3. Mal.

      Und wieder grüßt das Singleton-Pattern:

      Stattdessen könnte man auch gleich statische Klassen verwenden. Oder doch lieber DI.

      Deshalb bitte ich um Euer Feedback zu der allgemeinen Vorgehens- und Denkweise, meinem Workflow, zu Variablen- und Klassennamen, zur Strukturierung des Projektes und vielleicht ein Tipp, wie ich weitermachen kann.

      Probier mal, die DI-Idee zu verfolgen.

      dedlfix.

    3. Uff. Ich hoffe, ich bekomme keine Bauchschmerzen beim Versuch, das zu verdauen 😀

      Danke Dedlfix für die erste Feedback-Runde.

      Das Singleton-Pattern hat hier einen besonderen Nachteil: Damit es greift, muss deine ChangeLogReader Klasse explizit Config::getData() aufrufen. Es hat also eine feste Verbindung zur Config-Klasse. Sowas versucht man, mit Dependency Injection zu vermeiden.

      Grundsätzlich ist es ja immer so, dass dein PHP Hauptprogramm erstmal prozedural startet. D.h. um mit OOP loszulegen, musst Du erstmal irgendein Objekt erzeugen und darauf eine Methode aufrufen. Niemand verbietet Dir, das mehrfach zu tun, und den allgemeinen Setup kannst du, wie ich finde, gerne prozedural erledigen. Z.B. so:

      $params = new UrlParams(); $config = new Config($_SERVER['DOCUMENT_ROOT'] . '\config\config.json');

      $config->SelectLanguage($params->language); // siehe Unten

      $logContainer = new ChangeLogContainer($config); // Injizierte Abhängigkeit! $logContainer->loadChangeLogs();

      $app = new App($config, $logContainer); $app->ExecuteRequest($params);

      Die Konstruktoren von Config, UrlParams und ChangeLog können gerne alles tun, was nötig ist, um Konfiguration, URL-Parameter und die Sammlung der Logfiles bereitzustellen. Da Du das Lesen der einzelnen ChangeLog Datei mit simplexml durchführst, hast Du eigentlich gar keinen Reader, die Hauptaufgabe der Klasse ist eher, einen Container für alle geladenen XML Dateien bereitzustellen. Da das Laden der ChangeLogs eher umfangreich ist, hatte ich so das Gefühl, als sollte man das nicht im Konstruktor machen. Ich bin mir aber nicht sicher. Prinzipiell könnte der Konstruktor es mit erledigen. Vielleicht haben die Kollegen dazu eine andere Meinung.

      Achso - hast Du simplexml_load_file gesehen? Da kannst Du file_get_contents und simplexml_load_string zusammenfassen.

      Bei UrlParms bin ich Dedlfix' Vorschlag gefolgt, die Methoden wegzulassen. Der Konstruktur kann die drei Parameter, die für deine Anwendung relevant sind, in Properties von UrlParms ablegen. Er sollte sie aber vorher noch inhaltlich validieren, also z.B. keine ungültige Sprache zulassen. Es gibt andere Anwendungen, wo die URL Parameter sehr variieren oder auch POST Requeste mit urlencoded Daten im Requestbody verarbeitet werden, da müsste man vermutlich ein anderes Interface zu den Requestparametern bauen. Für deinen Einsatzzweck dürfte diese Klasse hinreichend sein. Was PHP nicht hat, sind readonly-Properties; wenn du sicherstellen willst, dass die URL Parameter durch deinen Code unveränderlich sind, musst Du sie in get-Funktionen kapseln. Vertraust Du Dir? ;-)

      Deiner Config-Klasse würde ich noch zwei Methoden verpassen: SelectLanguage($lang), die eine private Variable im Config-Objekt setzt, und eine Methode GetText($textCode) verpassen, die Dir zu einem TextCode den konfigurierten Text zur ausgewählten Sprache zurückgibt. Ob du die ausgewählte Sprache ständig in der URL mitschleppst, oder ob du sie in einem Cookie speicherst, kannst Du Dir noch überlegen.

      Für den weiteren Ablauf deiner Anwendung frage ich mich jetzt, woher die ExecuteRequest-Methode Application-Klasse wissen soll, was zu tun ist. Beim Erstaufruf wird sie vermutlich eine Übersicht der geladenen ChangeLog-Dateien anzeigen und pro Datei einen "Anzeigen" Button bieten, der die Seite mit dem Hash dieser Application neu aufruft.

      Dazu bräuchtest Du nun als nächstes eine Klasse LogIndexView - oder so. Alle Klassen, deren Job es ist, eine HTML Seite zu erzeugen, auf ein spezifisches Suffix wie 'View' enden lassen, kann der Übersicht dienen. Für ein vollständiges MVC Konzept müsstest Du nun noch einen LogIndexController bauen, der die eigentliche Arbeit macht.

      Application::ExecuteRequest hat die Aufgabe des Routers. In deinem Fall:

      if ( /* ist AppHash vorhanden */ )
      {
         /* Changelog zu diesem Hash anzeigen */
      }
      else
      {
         $controller = new LogIndexController($config);
         $controller->Show(new LogIndexView());
      }
      

      Der Controller extrahiert aus den geladenen ChangeLogs die Daten, die zur Anzeige des Index nötig sind, und übergibt sie dann zur Darstellung an den View. Dazu überlegst Du Dir am besten ein einheitliches API für Views, so dass Views austauschbar werden. Natürlich müssen sich Controller und View über die Struktur der anzuzeigenden Daten einig sein - es macht keinen Sinn, einen LogIndexView an einen ShowChangeLogController zur Anzeige zu übergeben.

      Ob Du dem Controller den View injizierst - wie hier - oder der Controller entscheiden muss, welcher View zu verwenden ist, das hängt vom Anwendungsfall ab. Manchmal kann der Router den View festlegen, nicht immer.

      An dieser Stelle zeigt sich, dass dein eifriges Laden aller ChangeLogs unnötig sein kann - den Aufruf von "getChangeLogs", den ich oben noch im Init stehen habe, gehört vielmehr in die Show-Methode des LogIndexControllers. Ein ShowChangeLogController muss ja nur eine einzige ChangeLog-Datei laden. Wie Du dann aus dem Hash auf die Changelog-Datei kommst, ist eine andere Frage :-))

      Hoffe, damit kommst Du erstmal weiter, ich feiere jetzt weiter Karneval…

      Rolf

      1. Vielen Dank Euch beiden!

        Ich muss das erst mal wieder verdauen. Ist für mich eine Menge an nützlicher Informationen, die ich erst mal einordnen muss. Ich glaube ich bin auf dem richtigen Weg. So langsam fühlt sich das auch nach OOP an. Vielen Dank für die Leitplanken und natürlich Alaaf!