MB: Konfiguration auslagern

moin Community

ich hatte gefragt wie ich Konfigurationsparameter in der Struktur laden kann. Ich hab mich für statische Klassen entschieden mit get-, set-Methode. Es bringt viele Vorteile.

Vorteil:

  1. Die Konfigurationsparameter sind in der Programmlogik enthalten und müssen nicht extra importiert werden.
  2. man kann beliebig viele Konfigurationsparameter hinzufügenund als Referenzpunkt verwenden
  3. Zugriffskontrolle.

Nachteil:

Alle konfigurationsparameter in eine statische Klasse zu packen is blöd. Und das ind viel un allmöglichen Kategorien (.z.B. Exception-Benachrichtigung, Messenger-Nachrichten, Router-Konfiguration etc.)

Lösungsansätze:

Ein Ansatz wäre mit Inheritance zu arbeiten wie class Router extends Config {}. Das wäre für die Unterteilung soweit okay, aber trotzdem ist es das gleicher wie class Exceptions extends Config {}. Natürlich könnte ich auch jede Klasse einzeln auf die gleiche weise programmieren aber ich seh da keinen Sinn dahinter. Wie löse ich das Unterteilungsproblem?

vlg MB

  1. Tach!

    Ein Ansatz wäre mit Inheritance zu arbeiten wie class Router extends Config {}.

    Ein recht schlechter Ansatz. Der Router muss nur mit den Konfigurationswerten arbeiten, er muss dazu nicht die Verwaltungsklasse erben und braucht sich auch nicht deren Methoden zu eigen zu machen. Es reicht, wenn er eine Instanz übergeben bekommt, aus der er sich seine Werte holen kann.

    dedlfix.

    1. moin dedlfix,

      Ein Ansatz wäre mit Inheritance zu arbeiten wie class Router extends Config {}.

      Ein recht schlechter Ansatz. Der Router muss nur mit den Konfigurationswerten arbeiten, er muss dazu nicht die Verwaltungsklasse erben und braucht sich auch nicht deren Methoden zu eigen zu machen. Es reicht, wenn er eine Instanz übergeben bekommt, aus der er sich seine Werte holen kann.

      Die Router-Konfiguration war ein Beispiel bezogen auf Inheritance um meine Frage zur verdeutlichen. Insofern ist es gleich was ich konfiguriere. Es geht hier in meine Frage darum wie ich konfiguriere. Und ich hab im Forum einen Ansatz unterbreitet.

      vlg MB

      1. Tach!

        Die Router-Konfiguration war ein Beispiel bezogen auf Inheritance um meine Frage zur verdeutlichen.

        Ob Router oder was anderes ist letztlich auch egal. Die Vorgehensweise des Erbens artfremder Dinge ist generell keine gute. Ein Stift muss nicht Teil meines Körpers werden, damit ich damit schreiben kann. Ich muss ihn lediglich aufnehmen oder übergeben bekommen und für die Dauer des Vorgangs in der Hand halten.

        dedlfix.

        1. moin dedlfix,

          Die Vorgehensweise des Erbens artfremder Dinge ist generell keine gute.

          Konfiguration A und Konfiguration B sind beide Konfigurationen und das ist doch nicht artfremd oder verstehe ich da was falsch.

          Ein Stift muss nicht Teil meines Körpers werden, damit ich damit schreiben kann. Ich muss ihn lediglich aufnehmen oder übergeben bekommen und für die Dauer des Vorgangs in der Hand halten.

          Die Beispiel Analogie habe ich verstanden, aber ich verstehe nicht wie sich dieses Verhalten auf diesen Kontext bezieht.

          vlg MB

          1. Tach!

            Die Vorgehensweise des Erbens artfremder Dinge ist generell keine gute.

            Konfiguration A und Konfiguration B sind beide Konfigurationen und das ist doch nicht artfremd oder verstehe ich da was falsch.

            Meintest du mit Router in class Router extends Config {} die Konfiguration für den Router oder den Router selbst? Bei RouterConfig wäre es nicht artfremd und man könnte darüber nachdenken, ob eine Vererbung in dem Fall sinnvoll ist, nachdem man mehr Einzelheiten dazu kennt. Wenn es aber der Router ist, dann ist die Konfigurationsverwaltungsklasse artfremt.

            dedlfix.

            1. moin dedlfix,

              ich stimme dir zu. Es verwunderte mich.

              ich z.B. nenne eine Klasse Foo mit Namespace Config, sodass ich Config\Foo in der Form Zugriff habe. Ich sehe keinen Sinn darin eine Klasse FooCfg zu nennen, wo sie ja schon im Namespace Config enthalten ist.

              Zur meiner Frage: warum siehst du keinen sin darin das man mit statischen Klassen Konfigurationsparameter allerart übergeben kann? Ich hab da ein bisschen rum gesponnen.

              namespace Config;
              class Config {
                private static $settings = []
                protected function set( $key, $value ) {
                  self::$settings[ $key ] = $value;
                }
                protected function get( $key ) {
                  return self::$settings[ $key ];
                }
              }
              
              namespace Config;
              class Foo extends Config {}
              

              ...so oder zur differenzierung so...

              namespace Config;
              class Foo extends Config {
                private static $foo = [];
                protected function set( $key, $value ) : void {
                  parent::set( self::$foo[ $key ], $value );
                }
                protected function set( $key, $value ) {
                  return parent::get( self::$foo[ $key ] );
                }
              }
              

              für mich eleganter und technisch genauer. Für konstruktive Kritik bin ich immer zuhaben.

              vlg MB

              1. Tach!

                ich z.B. nenne eine Klasse Foo mit Namespace Config, sodass ich Config\Foo in der Form Zugriff habe. Ich sehe keinen Sinn darin eine Klasse FooCfg zu nennen, wo sie ja schon im Namespace Config enthalten ist.

                Auch gut. Das war halt so nicht zu erkennen und Namespaces kamen mir grad nicht in den Sinn.

                Zur meiner Frage: warum siehst du keinen sin darin das man mit statischen Klassen Konfigurationsparameter allerart übergeben kann? Ich hab da ein bisschen rum gesponnen.

                Moment, du musst meine Aussagen in neuem Licht betrachten, ich hatte ja die Sache mit dem Namespace nicht beachtet.

                namespace Config;
                class Config {
                  private static $settings = []
                  protected function set( $key, $value ) {
                    self::$settings[ $key ] = $value;
                  }
                  protected function get( $key ) {
                    return self::$settings[ $key ];
                  }
                }
                
                namespace Config;
                class Foo extends Config {}
                

                Wenn es sich also reinweg um die Konfiguration dreht, dann ist das mit dem Vererben eine der möglichen Varianten. Config enthält generelle Methoden, die in Config\Foo oder Config\Router oder anderen spezialisierten derartigen Klassen benötigt werden.

                für mich eleganter und technisch genauer. Für konstruktive Kritik bin ich immer zuhaben.

                Eine andere Variante ist, dass man statt der Config-Klasse ein Repository erstellt und die eigentlichen Daten in POPO-Klassen[1] verwaltet, also nackige Klassen, die lediglich Eigenschaften für die Daten enthalten. Das Repository bekommt statt der allgemeinen set- und get-Methode konkretere Methoden, die mehr auf die Anwendung zugeschnitten sind. Zum Beispiel gibMirDatenbankZugangsdaten() oder gibMirLoggerKonfiguration(). Das Problem mit dem generischen get/set ist, dass man nun einen String braucht, um anzugeben, welche Daten man lesen oder schreiben möchte. Diese so genannten Magic Strings steuern nun den Programmablauf. Man muss ihre Namen wissen um zum gewünschten Ergebnis zu kommen und bekommt meist keine Autovervollständigung dafür. Diese generische Weise hat jedoch die Eigenschaft, dass man ohne irgendwelche (POPO-)Klassen anzupassen, beliebige Werte hinzufügen kann. Doch die Frage ist, ist es diese Flexibilität wert, dass dabei die Autovervollständigung und damit eine Hilfe zur Tippfehlervermeidung auf der Strecke bleibt.

                Man könnte die Betrachtung auch aus einem anderen Blickwinkel anstellen. Deine Variante hat gewisse Ähnlichkeiten mit einem Active-Record-System. Eine schwergewichtige Klasse kümmert sich um die Datenhaltung und vereint Daten und Methoden des Zugriffs. Mein anderer Vorschlag versucht diese direkte Beziehung zu vermeiden und trennt die eigentlichen Datenobjekte von der Klasse, die sich um die Verwaltung kümmert.

                dedlfix.


                1. Plain Old PHP Object ↩︎

                1. moin dedlfix,

                  Moment, du musst meine Aussagen in neuem Licht betrachten, ich hatte ja die Sache mit dem Namespace nicht beachtet.

                  Tschuldige. Mir war Bewusst, dass du nach meiner Richtigstellung eine veränderte Sicht auf mein Problem hast. Ich hab die Eingangsfrage in kurzform direkt an Dich gestellt, und zwar in gedachten Unwissen der Konkretisierung meiner Frage, weil ich keine AW von Dir erhalten. Sorry, mein schweres Deutsch. Ich hoffe dir sind meine Beweggründe klarer. Zurück zum Thema:

                  Eine andere Variante ist, dass man statt der Config-Klasse ein Repository erstellt und die eigentlichen Daten in POPO-Klassen[^1] verwaltet, also nackige Klassen, die lediglich Eigenschaften für die Daten enthalten.

                  Hast du literatur davon, oder reicht ein kleines Metasyntaktisches Beispiel? Also Spannend finde ich das. Danke dafür.

                  vlg MB

                  1. Tach!

                    Eine andere Variante ist, dass man statt der Config-Klasse ein Repository erstellt und die eigentlichen Daten in POPO-Klassen[^1] verwaltet, also nackige Klassen, die lediglich Eigenschaften für die Daten enthalten.

                    Hast du literatur davon, oder reicht ein kleines Metasyntaktisches Beispiel? Also Spannend finde ich das. Danke dafür.

                    Oftmals braucht man mehrere Werte für eine Konfiguration, und wenn, dann braucht man sie selten einzeln. Für jeden Wert ein extra Funktionsaufruf zu starten ist viel Wiederholarbeit. Für dich, die Aufrufe zu schreiben, und ausgeführt werden müssen sie auch noch. Alle Werte mit einem Aufruf zu holen erspart einiges an Arbeit. Der Start ist, sich erstmal die Klasse zu erstellen, die die Werte transportieren soll.

                    namespace Config;
                    class Database {
                      public host;
                      public database;
                      public username;
                      public password;
                    }
                    

                    Das ist das POPO-Objekt für die Werte. Deine Datenbank-Klasse muss nur diese Werte bekommen. Die muss nicht wissen, dass es da einen Konfigurationsspeicher gibt, wo diese Werte herkommen. Sie bekommt die Daten gebrauchsfertig hereingereicht. Wobei die Erstellung des DSN-Strings datenbankspezifisch ist und die Db\Mysql-Klasse sie sich in einer Hilfsmethode (buildDsn) selbst zusammenbauen kann. Alternativ könnte man auch den fertigen DSN-String in der Konfiguration ablegen und die POPO-Klasse hätte dann $dsn statt $host und $database als Eigenschaften.

                    namespace Db;
                    class Mysql {
                      private $config;
                    
                      function __construct(Config\Database $config) {
                        $this->config = $config;
                      }
                      
                      private function connect() {
                        $this->pdo = new PDO($this->buildDsn($this->config), 
                          $this->config->username, $this->config->password);
                        // ...
                      }
                    
                      // andere Dinge die so eine Datenbankklasse tun muss
                    }
                    

                    Das Repository ist dafür zuständig, die Daten zu besorgen, wenn ein Aufrufer sie haben möchte.

                    namespace Config;
                    class Repository {
                      private $database = null;
                    
                      public GetDatabaseConfig() {
                        if ($this->database == null) {
                          $this->database = new Config\Database();
                          // hier müssen die Daten aus der Konfigurationsdatenquelle
                          // irgendwie den Weg in das Objekt finden.
                        }
                        return $this->database;
                      }
                    }
                    

                    Und an anderer Stelle in deinem Programm, da wo du eine Datenbank-Instanz erstellen möchtest, muss es eine Instanz des Config-Repository geben, aus dem du deine Daten bekommst, die der Konstruktor benötigt.

                    $database = new Database\Mysql($configRepository->GetDatabaseConfig());
                    

                    Das ist alles keine Raketenwissenschaft. Der Trick ist nur, die Dinge so einfach wie möglich zu halten, sprich: die Akteure weitestgehend frei von Wissen anderer Akteure zu halten.

                    dedlfix.

                    1. moin dedlfix,

                      genauso habe ich das zuvor gemacht, mit genau den gleichen Beweggründen. Rechtherzlichen Dank für die Bestätigung und auch für den Code.

                      vlg MB

  2. Moin,

    die Frage ist doch die: Brauchen wir jeweils Instanzen für Routing und Konfiguration? Nein brauchen wir nicht. Also gibts da auch nichts zu vererben. Und schließlich ist die Klasse zu der geroutet werden soll, auch nur eine Eigenschaft in der Konfiguration. Daraus ergibt sich:

    Die Konfiguration einschließlich Routingtable muss bereis vor der Erstellung einer Instanz der Responseklasse für den wahlfreien Zugriff bereitstehen. Und damit diese Instanz in der Lage ist, auf die Konfiguration zugreifen zu können, bekommt diese Instanz Methoden damit sie das tun kann.

    Weder Routingtable noch Konfiguration brauchen Methoden. Schöne Grüße.

    1. moin pl,

      die Frage ist doch die: Brauchen wir jeweils Instanzen für Routing und Konfiguration? Nein brauchen wir nicht. Also gibts da auch nichts zu vererben.

      du hast natürlich in gewisser hinsicht recht. Ohne zweifel. Es gibt aber bestimmt Systeme wo es Sinnvoll ist, es so zu machen. Ich hab aus dem Bauch heraus Vorteile genannt aber auch Nachteile die es nicht begünstigen, diese Vorgehensweis zuverwenden. Ich nehme an von solchen Systemen bist du ausgegangen.

      Und schließlich ist die Klasse zu der geroutet werden soll, auch nur eine Eigenschaft in der Konfiguration.

      Nochmal: Es geht mir nicht um einen Konkreten Fall den ich ansprach. Deswegen habe ich nachher noch die Metasyntaktischen Variablen wie Foo und Bar verwendet 😉.

      vlg MB

      PS: Wenn ich dich richtig verstanden habe versteht sich. Das ist bei mir ja so n Spezialfall mit Verstehen.

      1. Es gibt aber bestimmt Systeme wo es Sinnvoll ist, es so zu machen.

        Du meinst, dass bspw, eine Konfiguration eine Methode braucht um sich selbst zu ändern oder Eigenschaften nach außerhalb der Klasse zu bringen? Dann wäre immer noch die Frage offen, ob eine statische Methode reicht oder ob erst eine Instanz erstellt werden muss.

        Nehmen wir mal an die Instanz der Controller-Class will den Titel ändern. Da wärs doch zeckmäßig, wenn diese Instanz eine eigene Methode hätte und ebnso der Aufruf dieser einen Methode reichen würde:

        $self->eav('title','Neuer Titel');

        Aber was letztendlich die eav()-Methode alles tun muss, z.B. eine Instanz der Konfiguration erstellen oder eine statische Methode der Config-Class aufzurufen ist doch letztendlich egal oder?

        Schöne Grüße.

        PS: Remote Prozedure Call zum Prüfen der Konfiguration

        D:\>c.pl RPC
        Remote CMD auf dem Host
        --attribute, -a: Zeigt Attribut+Value einer Entity in Konfiguration
        --base, -ba: Name der Datenbank für Option --sql
        --binary, -bi: Erzeuge die Konfiguration als Binary
        --cmd, -c: Freies Kommando im aktuellen Verzeichnis
        --dump, -d: Dump Response Object
        --entity, -e: Zeigt Attribute einer Entity in Konfiguration
        --files, -f: Lokale Dateien für Upload
        --head, -he: HEAD Request auf URL
        --host, -ho: rolfrost.de oder rolfrost
        --irc, -i: Chatserver starten
        --request, -r: HTTP Request auf den angegebenen URL oder auf alle URLs
        --sql, -s: SQL Anweisung, erfordert --base
        --urls, -u: Listet URLs in Konfiguration
        
        D:\>c.pl RPC -host rolfrost.de -ent /uvd.html
        body
        class
        descr
        no_cache
        no_tt
        parent
        title
        url
        D:\>c.pl RPC -host rolfrost.de -ent /uvd.html -att title
        UvD in der Nationalen Volksarmee, Grenztruppen der DDR
        D:\>c.pl RPC -host rolfrost.de -ent /uvd.html -att class
        DBFileResponse
        
        1. moin pl,

          Es gibt aber bestimmt Systeme wo es Sinnvoll ist, es so zu machen.

          Du meinst, dass bspw, eine Konfiguration eine Methode braucht um sich selbst zu ändern oder Eigenschaften nach außerhalb der Klasse zu bringen?

          Ja für diese Variante. erst setzen und dann geben wenn es angefordert wird.

          Dann wäre immer noch die Frage offen, ob eine statische Methode reicht oder ob erst eine Instanz erstellt werden muss.

          IMO lohnen statische Klassen sich da echt. Config::get( 'foo' ); brauch nicht instanziiert werden, können beliebig viele Config::set() einträge gemacht werden mit einer einzigen Klasse.

          Nehmen wir mal an die Instanz der Controller-Class will den Titel ändern.

          Das ist aber n anderes Szenario. Bei meinem Problem geht es ausschließlich um Konfiguration und Inizialisierungen.

          vlg MB

          1. Nehmen wir mal an die Instanz der Controller-Class will den Titel ändern.

            Das ist aber n anderes Szenario. Bei meinem Problem geht es ausschließlich um Konfiguration und Inizialisierungen.

            Genau deswegen ja mein Beispiel für den Remote Prozedure Call: Kommt der RPC Request am Server an, wird serverseitig eine Instanz derjenigen Klasse erstellt die an den für RPC zuständigen URL gebunden ist per Konfiguration. Und diese Instanz hat genauso wie jede andere Instanz die bei beliebigen Requests auf konfigurierte URLs erstellt wird, wahlfreien Zugriff auf die gesamte Konfiguration weil Letztere bereits vor der Instanzerstellung in den Hauptspeicher geladen wurde.

            Ob bei diesem Zugriff eine Methode like Config:get() oder Config::set() dazwischengeschaltet wird oder nicht, ist im Effekt letztendlich völlig Wurscht. Entscheidend ist, dass sämtliche Aktionen, auch die der Konfiguration betreffende, über Methoden derjenigen Instanz laufen die den Request entgegennimmt und die Response ausliefert (Single-Responsibility-Prinzip, SRP).

            Somit wird auch für RPC keine Extrawurst gebruzzelt, RPC ist voll in das Framework integriert. Es hat denselben Ablauf Request+Response wie jeder andere beliebige Request auch, ganz egal ob der Wetterbericht ausgegeben wird oder die Konfiguration eines bestimmten Realms, Änderungen inbegriffen.

            Egal wie falsch, Hauptsache einheitlich 😉

            1. Tach!

              Entscheidend ist, dass sämtliche Aktionen, auch die der Konfiguration betreffende, über Methoden derjenigen Instanz laufen die den Request entgegennimmt und die Response ausliefert (Single-Responsibility-Prinzip, SRP).

              SRP bedeutet nicht, dass einer für alle Aufgaben zuständig ist, sondern dass eine Klasse oder eine Methode für genau eine Aufgabe und nichts anderes zuständig ist.

              dedlfix.

              1. moin dedlfix,

                [...] eine Klasse oder eine Methode für genau eine Aufgabe und nichts anderes zuständig ist.

                Das sagst du was womit ich gleich einleitend einen neuen Thread aufmachen werde.

                vlg MB

          2. Moin,

            Was Du jedoch vererben kannst, ohne eine Klasse zu bemühen, sind sogenannte Defaults. Default steht für einen Wert der fehlt und auf die Konfiguration bezogen heißt das, daß eine Eigenschaft nicht gesetzt wurde. So könnte es einen ganzen Default-Block geben, z.B.

            [default]
            title = Hab kein Title
            descr = Hab kein Beschreibung
            class = NotFound
            email = sos@excample.com
            usw..
            

            und diese Eigenschaften gelten sofern sie nicht anderweitig festgelegt wurden. Da wäre nur zu regeln, ob die Überschreibung generell für jede Eigenschaft erfolgen soll z.B. mit array_merge() oder nur für bestimmte Eigenschaften.

            Vererbung ist also nicht eine Frage der objektorientierten Programmierung sondern eine Frage wie Du selbst als Programmierer mit Daten und abstrakten Datentypen umgehst. MfG

            1. moin pl,

              super Beispiel und super vorgehensweise. Mir scheint es aber das diese Vorgehensweise außerhalb der Progammlogik ist. Intern in PHP-Programmen habe ich noch nie sowas in der Art gesehen. In INI-Dateien schon aber das is ja auserhalb der Programmlogik. Es muss erst importiert werden damit es zu der Programlogik gehört. Eben genau das möchte ich ja vermeiden. Aber bitte klär mich auf wenn ich falsch liege.

              vlg MB