Ankündigung

Einklappen
Keine Ankündigung bisher.

Config Daten für alle Klassen bereitstellen?

Einklappen
X
  • Filter
  • Zeit
  • Anzeigen
Alles löschen
neue Beiträge

  • Config Daten für alle Klassen bereitstellen?

    Hallo,

    irgendwie stehe ich im Moment etwas auf dem Schlauch, wie ich folgendes Problem sauber mit PHP abbilden kann.

    Ich habe drei Klassen:

    Code:
    cBelegposition
    + intLfdnr
    + intMenge
    + strBezeichnung
    ...
    
    cBelegkopf
    + strBelegnummer
    + objBeleposition()
    ...
    
    cBasics
    + objDatenbankverbindung
    + intDebugmode
    ...
    ...

    Wie kriege ich es jetzt hin, das ich in den Klassen cBelegposition und cBeleg jeweils Zugriff auf die cBasics Klasse habe?
    Ich könnte natürlich cBeleg und cBelegposition von der Klasse cBasics ableiten, dann habe ich doch aber bei z.B. 1000 Positionen einen irren Overhead.
    Alternativ könnte ich auch nur die cBeleg Klasse von der cBasics Klasse ableiten und dann z.B. meine Datenbankverbindung als Parameter beim Methodenaufruf an die cBelegposition durchreichen. Das erscheint mir aber auch nicht optimal...
    Für einen Denkanstoß wäre ich sehr dankbar. Die Verwendung von einem aktuellen Framework kommt im Moment noch nicht in Frage.

    Gruß Braeu


  • #2
    Zitat von Braeu Beitrag anzeigen
    z.B. meine Datenbankverbindung als Parameter beim Methodenaufruf an die cBelegposition durchreichen. Das erscheint mir aber auch nicht optimal...
    Was stört dich daran? Methodenparameter sind genau dafür gemacht.

    Dependency Injection ist im Prinzip nichts anderes und wird als gutes Design angesehen.
    Über 90% aller Gewaltverbrechen passieren innerhalb von 24 Stunden nach dem Konsum von Brot.

    Kommentar


    • #3
      Anmerkung dazu:

      Zitat von Braeu
      Alternativ könnte ich auch nur die cBeleg Klasse von der cBasics Klasse ableiten
      Vererbung ist nur dann zu verwenden wenn es eine "ist ein(e)" Beziehung ist. Bsp: BMW ist ein Auto, Hans ist ein Mann, Mann ist ein Mensch etc.. Alles andere ist falsch. Belegposition ist keine (Basis-)Konfiguration.
      Debugging: Finde DEINE Fehler selbst! | Gegen Probleme beim E-Mail-Versand | Sicheres Passwort-Hashing | Includes niemals ohne __DIR__
      PHP.de Wissenssammlung | Kein Support per PN

      Kommentar


      • #4
        Hallöchen,

        wieso lagerst du verarbeitende Prozesse nicht in eigene Klassen aus? Das klingt ein bisschen so als wolltest du deine Business-Logik direkt in die Entitäten packen.

        Die Verwendung von einem aktuellen Framework kommt im Moment noch nicht in Frage.
        Hier geht es im Prinzip um dein grundlegendes Klassendesign. Auch wenn du kein Framework verwenden möchtest, solltest du dich von vorhandenen Lösungen inspirieren lassen. Hochwertigen Code zu stöbern hilft ungemein dabei ein gutes Verständnis für sauberes Klassendesign zu entwickeln.

        Viele Grüße,
        lotti

        Kommentar


        • #5
          Hallo,

          und erst einmal vielen Dank für eure interessanten Antworten!

          Dependency Injection ist im Prinzip nichts anderes und wird als gutes Design angesehen.
          Den Begriff musste ich erstmal googeln. Der erste Treffer (http://phpmagazin.de/Mit-Dependency-...eren-5434.html) beschreibt mit dem ersten Beispiel meinen bisherigen Ansatz. Wie dann weiter beschrieben ist es dan natürlich besser. Hier muss nun nur drauf achten, das ich mein "cBasisc" Objekt nicht schon im Constructor der cPositions Klasse mitgebe sondern erst dann, wenn ich eine Methode aufrufe (also Beispiel 3 in dem Link). Sonst hätte ich ja wieder 1000mal mein cBasics Objekt angelegt.

          Zitat von Braeu
          Alternativ könnte ich auch nur die cBeleg Klasse von der cBasics Klasse ableiten



          Vererbung ist nur dann zu verwenden wenn es eine "ist ein(e)" Beziehung ist. Bsp: BMW ist ein Auto, Hans ist ein Mann, Mann ist ein Mensch etc.. Alles andere ist falsch. Belegposition ist keine (Basis-)Konfiguration.
          100% Zustimmung, darum werd ich ja auch nicht mit der cPosiionsklasse die cBelegklasse beerben.

          wieso lagerst du verarbeitende Prozesse nicht in eigene Klassen aus? Das klingt ein bisschen so als wolltest du deine Business-Logik direkt in die Entitäten packen.
          Falls ich dich jetzt richtig verstanden habe: Wo soll ich die Logik denn sonst hinlegen? Meine Klasse cPosition neben den (privaten) Eigenschaften wie Menge, Nummer usw., den Getter und Setter Methoden auch Methoden womit ich z.b. die Position in der Datenbank sichere oder per DB Abfrage offene Mengen ermittle.

          Hier geht es im Prinzip um dein grundlegendes Klassendesign.
          Richtig, PHP spielt hier nur am Rande eine Rolle. Ich habe zu dem Thema noch mit einem Kumpel gesprochen der C++ programmiert. Da kam dann der Ansatz, einen Pointer auf die cBasics Klasse zu übergeben. Gott sei Dank geht das in PHP ja nicht...

          Auch wenn du kein Framework verwenden möchtest, solltest du dich von vorhandenen Lösungen inspirieren lassen. Hochwertigen Code zu stöbern hilft ungemein dabei ein gutes Verständnis für sauberes Klassendesign zu entwickeln.
          Auch richtig. Mein Problem ist hier nur, dass das ganze zunächst als Teil einer vorhandenen Lösung Funktionieren muss. Gleichzeitig war meine Überlegung, damit den Grundstein für eine Neuanfang zu legen, für den dann wahrscheinlich ZF als Grundlage genommen wird.

          Viele Grüße

          Braeu

          Kommentar


          • #6
            Hallöchen,

            Zitat von Braeu Beitrag anzeigen
            Den Begriff musste ich erstmal googeln. Der erste Treffer (http://phpmagazin.de/Mit-Dependency-...eren-5434.html) beschreibt mit dem ersten Beispiel meinen bisherigen Ansatz. Wie dann weiter beschrieben ist es dan natürlich besser. Hier muss nun nur drauf achten, das ich mein "cBasisc" Objekt nicht schon im Constructor der cPositions Klasse mitgebe sondern erst dann, wenn ich eine Methode aufrufe (also Beispiel 3 in dem Link). Sonst hätte ich ja wieder 1000mal mein cBasics Objekt angelegt.
            Dependency Injection ist primär ein Werkzeug um eine lose Kopplung zwischen deinen Klassen zu schaffen. Damit kann sich jede Klasse auf ihre Kernfunktionalität fokussieren. Eines der Grundprinzipien (SRP) besagt, dass jede Klasse nur eine einzige Aufgabe haben sollte. Eine Fahrzeug-Klasse sollte also nicht gleichzeitig Werkstatt, Lackiererei und Händler sein - um es vereinfacht auszudrücken.

            Zitat von Braeu Beitrag anzeigen
            100% Zustimmung, darum werd ich ja auch nicht mit der cPosiionsklasse die cBelegklasse beerben.
            Vererbung ist ein sehr mächtiges Werkzeug, dennoch wird es oft völlig falsch verwendet. In diesem Zusammenhang fällt häufig der Satz: "Komposition statt Vererbung".

            Zitat von Braeu Beitrag anzeigen
            Falls ich dich jetzt richtig verstanden habe: Wo soll ich die Logik denn sonst hinlegen? Meine Klasse cPosition neben den (privaten) Eigenschaften wie Menge, Nummer usw., den Getter und Setter Methoden auch Methoden womit ich z.b. die Position in der Datenbank sichere oder per DB Abfrage offene Mengen ermittle.
            Um offene Mengen zu berechnen sind sicherlich größere Zusammenhänge notwendig, wie bspw. die zu einem Beleg verfügbaren Bestellungen und Lieferungen. D.h. es muss ein völlig eigener Kontext für diesen Anwendungsfall geschaffen werden. In diesem Fall würde ich eine separate Klasse erstellen, welche dafür zuständig ist, diese "offene Menge" anhand eines Belegs & einer Belegposition zu ermitteln.

            Zitat von Braeu Beitrag anzeigen
            Richtig, PHP spielt hier nur am Rande eine Rolle. Ich habe zu dem Thema noch mit einem Kumpel gesprochen der C++ programmiert. Da kam dann der Ansatz, einen Pointer auf die cBasics Klasse zu übergeben. Gott sei Dank geht das in PHP ja nicht...
            Das halte ich für falsch, weil der von dir - sehr dürftig - beschriebene Anwendungsfall vermutlich sauberer durch eine separate Klasse abzubilden ist.

            Zitat von Braeu Beitrag anzeigen
            Auch richtig. Mein Problem ist hier nur, dass das ganze zunächst als Teil einer vorhandenen Lösung Funktionieren muss. Gleichzeitig war meine Überlegung, damit den Grundstein für eine Neuanfang zu legen, für den dann wahrscheinlich ZF als Grundlage genommen wird.
            Ich würde mich vor diesem Schritt intensiv mit den aktuellen Frameworks auseinandersetzen.

            Viele Grüße,
            lotti

            Kommentar


            • #7
              Man braucht heute nicht mehr wirklich ein full-featured Framework. Es reicht sich seine Komponenten via Composer zusammenzustellen. Da kann man sich dann auch quer beim ZendFramework, Symfony und anderen bedienen und diese Komponenten beliebig mischen. In meinen Projekten komme ich sogar (bis auf ganz wenige Ausnahmen) komplett ohne die Komponenten von Symfony und Zend aus, weil es für fast jede Komponente eine bessere unabhängige Komponente gibt. Aber das ist ganz dir überlassen!

              Zum Thema Dependency Injection:
              Nimm einen DI Container. Nicht so einen Müll wie Pimple, der dir vielleicht schnell bei Google entgegenspringt. Nimm sowas wie PHP-DI. Das sieht auch nur im ersten Moment etwas kompliziert ist. Eigentlich ist das nur sowas, was du ggf als Autoloading fuer Klassen kennst - nur eben einen Schritt weiter gedacht.

              PHP-Code:
              composer require mnapoli/php-di ~4.3 
              Nahmen wir an, dass du eine Art Controller- oder Service-Klasse hast. Diese Klasse bekommt generell alles via Constructor-Injection übergeben (verabschiede dich von Singleton - komplett). Die Klassen, die hier via CI übergeben werden, haben ggf. wieder solche Abhängigkeiten und so weiter. Da kommt jetzt ein DIC ins Spiel. Irgendwo auf der untersten Ebene deiner APP erstellst du dann diesen DIC:

              PHP-Code:
              $builder = new ContainerBuilder();
              $builder->addDefinitions(__DIR__.'/config/di.php');
              $container $builder->build(); 
              Dann holst du dir die Controller-Klasse und führst eine Methode aus:

              PHP-Code:
              $container->call(function (MeinController $controller) {
                  
              $controller->methode/* ... */ );
              }); 
              PHP-DI hat sich jetzt schon um alles gekümmert. Dein MeinController ist fertig initialisiert und alle Abhängigkeiten sind injiziert. Bei einigen Klassen geht das nicht so automatisch, daher brauchst du sowas wie die config/di.php in der du beschreibst, wie die Problemklassen initialisiert werden sollen.

              Jetzt kommen wir langsam zu deinem cBelegkopf. Der wird über eine Factory erzeugt. Das ist der einzige Ort (Factories im Allgemeinen) wo du noch mal mit dem DIC in Berührung kommst:

              PHP-Code:
              class MyFactory {
                  
              /** @var Container */
                  
              private $container;

                  
              /**
                   * @param Container $container
                   */
                  
              public function __construct(Container $container) {
                      
              $this->container $container;
                  }

                  
              /**
                   * @return CBelegKopf
                   */
                  
              public function create() {
                      return 
              $this->container->make(CBelegKopf::class);
                  }

              Hast du das Prinzip verstanden?

              Deine Applikation ist nachher so sauber, wie es nur möglich ist. Deine IDE (wie PHPStorm) kann den gesamten Quellcode verstehen, weil aus Sicht von PHPStorm für die statische Codeanalyse keine Magie im Spiel ist. Das ist auch kein komischer Modetrend. In der Javawelt ist das STANDARD und im PHP-Umfeld kommt das auch langsam bei den Leuten an.
              Standards - Best Practices - AwesomePHP - Guideline für WebApps

              Kommentar


              • #8
                Ich merke schon, ich muss doch nochmal das Buch zu den Entwurfsmustern raussuchen...

                Um offene Mengen zu berechnen sind sicherlich größere Zusammenhänge notwendig, wie bspw. die zu einem Beleg verfügbaren Bestellungen und Lieferungen. D.h. es muss ein völlig eigener Kontext für diesen Anwendungsfall geschaffen werden. In diesem Fall würde ich eine separate Klasse erstellen, welche dafür zuständig ist, diese "offene Menge" anhand eines Belegs & einer Belegposition zu ermitteln
                OK, aber um im Beispiel zu bleiben: die offene Menge ist doch aber ein Wert (ich schreibe bewusst nicht Eigenschaft), der direkt zur Position gehört. So wie im Autobeispiel die Farbe oder passender das Autoalter als Differenz zwischen Baujahr und dem aktuellen Datum (d.h. berechnet).
                Oder meinst du es so, dass ich (wie im Link zu "Komposition statt Vererbung") mit einem Interface zur Berechnung der offenen Menge arbeite um so die eigentliche Berechnung aus der cPositionsklasse raus zu halten und in den eigenen Berechnungsklasse(n) verschiedene (Berechnungs-) Methoden implementieren kann?

                Ich würde mich vor diesem Schritt intensiv mit den aktuellen Frameworks auseinandersetzen.
                Stimmt schon, nur wenn ich es jetzt möglichst Richtig mache, dann hoffe ich zumindest ein geeignetes Fundament für das M im MVC gelegt zu haben.

                Gruß Jens

                Kommentar


                • #9
                  rkr
                  Vielen Dank für deine ausführliche Antwort. Das Thema Composer kannte ich noch nicht, bis jetzt gab es immer entweder ZF komplett oder gar nicht.
                  Ich habe mir nun mal php-di versucht näher anzusehen. Also neues Unterverzeichnis in der WEB Root und php-di drinnen installiert, hat per Composer problemlos geklappt. Dann habe ich analog der php-di Doku eine config.php angelegt als php Array:
                  PHP-Code:
                  <?php
                  return [
                      
                  'db.host'           => 'localhost',
                      
                  'db.port'           => 5000,
                      
                  'report.recipients' => [
                          
                  'bob@acme.example.com',
                          
                  'alice@acme.example.com'
                      
                  ],
                  ];
                  In meiner grenzenlosen Naivität schreibe ich dann in einen neue test.php Datei das:
                  PHP-Code:
                  <?php
                  $builder 
                  = new ContainerBuilder();
                  $builder->addDefinitions('config.php');

                  $container $builder->build();

                  echo 
                  $container->get('db.port')

                  ?>
                  Geht natürlich nicht, wie auch sollt er die Klasse finden. Ein
                  PHP-Code:
                  require_once('vendor/mnapoli/php-di/src/DI/Container.php'); 
                  hilft auch nicht weiter, dann gib er einen Fehler in der Container.php. Ich mache hier also noch was grundlegendes falsch bzw verstehe es nicht. Zu der Factory komme ich überhaupt noch nicht (wie im letzten Post schon beschrieben sollte ich mir dafür auch sinnvoller Weise nochmal generell die Entwurfsmuster ansehen..).

                  Gruß Jens

                  Kommentar


                  • #10
                    Du brauchst noch einen Classloader.

                    Ersetze

                    PHP-Code:
                    require_once('vendor/mnapoli/php-di/src/DI/Container.php'); 

                    einfach durch

                    PHP-Code:
                    require_once('vendor/autoload.php'); 
                    vg
                    jack
                    -

                    Kommentar


                    • #11
                      Hi, Composer stellt dir bereits einen voll eingerichteten Autoloader bereit.
                      Damit sieht deine index.php dann so aus:

                      PHP-Code:
                      <?php
                      require __DIR__.'/vendor/autoload.php'// ... anpassen, wenn dein vendor-verzeichnis woanders liegt

                      $builder = new ContainerBuilder();
                      $builder->addDefinitions('config.php');

                      $container $builder->build();

                      echo 
                      $container->get('db.port');
                      Standards - Best Practices - AwesomePHP - Guideline für WebApps

                      Kommentar


                      • #12
                        OK, jetzt klappt es in meinem Trivialbeispiel - vielen Dank!
                        Ein kleiner Fehler ist noch in in rkt's Code, ich musste folgendes schreiben:

                        PHP-Code:
                        <?php
                        $builder 
                        = new \DI\ContainerBuilder();
                        //$builder = new ContainerBuilder();  <- kriegt der Autoloader nicht hin
                        ?>
                        Ich denke auch, das ich nun weiß, worauf das mit der Factory hinausläuft und habe das mal in einem Autobeispiel umgesetzt. Hier zunächst meine cAutoklasse:
                        PHP-Code:
                        <?php
                        require_once 'cReifen.php';

                        class 
                        cAuto{
                            public 
                        $farbe 'rot';
                            public 
                        $reifen;

                            public function 
                        __construct(){
                                
                        $this->reifen[] = new cReifen('VL');
                                
                        $this->reifen[] = new cReifen('VR');
                                
                        $this->reifen[] = new cReifen('HL');
                                
                        $this->reifen[] = new cReifen('HR');
                            }

                            public function 
                        getPort(){
                                return 
                        $this -> container -> get('db.port');
                            }
                        }
                        ?>
                        und die cReifen Klasse (nur wegen der Vollständigkeit):
                        PHP-Code:
                        <?php
                        class cReifen{
                            public 
                        $durchmesser '17Zoll';
                            public 
                        $anbauort;

                            public  function 
                        __construct($anbauort){
                                
                        $this -> anbauort $anbauort;
                            }
                        }
                        ?>

                        Jetzt die Autofabrik:
                        PHP-Code:
                        <?php
                        require_once 'cAuto.php';

                        class 
                        cAutoFactory {
                            
                        /** @var Container */
                            
                        private $container;

                            
                        /**
                             * @param Container $container
                             */
                            
                        public function __construct(DI\Container $container) {
                                
                        $this->container $container;
                            }

                            
                        /**
                             * @return cAuto
                             */
                            
                        public function createAuto() {
                                return 
                        $this->container->make(cAuto::class);
                            }
                        }
                        ?>
                        und noch die vollständige test.php
                        PHP-Code:
                        <?php
                        require_once __DIR__.'/vendor/autoload.php'// ... anpassen, wenn dein vendor-verzeichnis woanders liegt
                        require_once 'cAutofactory.php';

                        $builder = new \DI\ContainerBuilder();
                        //$builder = new ContainerBuilder();
                        $builder->addDefinitions('config.php');

                        $container $builder->build();

                        echo 
                        'aus Test.php '.$container->get('db.port');

                        $factory = new cAutofactory($container);
                        $objAuto $factory -> createAuto();

                        echo 
                        'aus Auto '.$objAuto -> getPort();
                        ?>
                        Das erste Echo funktioniert, das zweite nicht. Er kennt an der Stelle den Container nicht. Wie stelle ich es nun an, das ich in meiner cAuto Klasse Zugriff auf den Inhalt des DI-Conainers habe?

                        Noch eine Frage zum Composer: kann ich meine eigenen Klassen da auch rein kopieren, damit der Autoloader des Composers die auch verarbeitet? Sonst muss ich ja wieder überall mit require_once arbeiten.

                        Viele Grüße und nochmals Dank!

                        Braeu

                        Kommentar


                        • #13
                          Hallöchen,

                          Zitat von Braeu Beitrag anzeigen
                          OK, jetzt klappt es in meinem Trivialbeispiel - vielen Dank!
                          Ein kleiner Fehler ist noch in in rkt's Code, ich musste folgendes schreiben:

                          PHP-Code:
                          <?php
                          $builder 
                          = new \DI\ContainerBuilder();
                          //$builder = new ContainerBuilder(); <- kriegt der Autoloader nicht hin
                          ?>
                          Das ist kein Fehler, sondern liegt schlicht und ergreifend daran, dass die Klasse ContainerBuilder nicht bekannt ist. Mit dem use-Schlüsselwort kannst du Klassen in den aktuellen Namensraum importieren:

                          PHP-Code:
                          namespace MyProject\Example;

                          use 
                          DI\ContainerBuilder;

                          // ..

                          $builder = new CotainerBuilder

                          Zitat von Braeu Beitrag anzeigen
                          Noch eine Frage zum Composer: kann ich meine eigenen Klassen da auch rein kopieren, damit der Autoloader des Composers die auch verarbeitet? Sonst muss ich ja wieder überall mit require_once arbeiten.
                          Klar. Das ist Sinn und Zweck von Composer. Composer PSR-4 Autoloading.

                          Viele Grüße,
                          lotti

                          Kommentar


                          • #14
                            Wie stelle ich es nun an, das ich in meiner cAuto Klasse Zugriff auf den Inhalt des DI-Conainers habe?
                            Mach das besser nicht. Es ist nicht gut den DI-Container mit anderen Klassen zu verdrahten (Stichwort ServiceLocator → Antipattern). Lies Dich in das Thema Dependency Injection/Dependency Injection Container (DI/DIC) und Inversion of Control IoC am besten zuerst richtig gut ein bevor Du anfängst mit dem PHP-DI zu experimentieren. Das lernt mal normalerweise nicht mal so eben an 2 Nachmittagen


                            Noch eine Frage zum Composer: kann ich meine eigenen Klassen da auch rein kopieren, damit der Autoloader des Composers die auch verarbeitet? Sonst muss ich ja wieder überall mit require_once arbeiten.
                            Du hast im Prinzip zwei Möglichkeiten:

                            1) Direkt, indem Du die Namespaces quasi on-the-fly zum autoloader hinzufügst. Das ist z.B. ganz praktisch wenn Du mal auf die schnelle was ausprobieren, oder ein vorhandenes Mapping einfach überschreiben willst:

                            PHP-Code:
                            $loader = require 'vendor/autoload.php';
                            //$loader = setPsr4($namespace, $path);
                            $loader->setPsr4('''src/');
                            $loader->setPsr4('DeinNamespace\\''src/DeinNamespace/'); 
                            2) oder über entsprechende Einträge in der composer.json

                            Code:
                            {
                               "autoload": {
                                    "psr-4": {
                                        "": "src/",
                                        "DeinNamespace \\":"src/DeinNamespace/"
                                    }
                                }
                            }
                            damit die Änderungen übernommen werden mußt Du auf der Kommandozeile noch folgenden Befehl aufrufen
                            Code:
                            composer update -o
                            https://getcomposer.org/doc/03-cli.md#update

                            vg
                            jack
                            -

                            Kommentar


                            • #15
                              Zitat von jack88 Beitrag anzeigen
                              Mach das besser nicht. Es ist nicht gut den DI-Container mit anderen Klassen zu verdrahten (Stichwort ServiceLocator → Antipattern). Lies Dich in das Thema Dependency Injection/Dependency Injection Container (DI/DIC) und Inversion of Control IoC am besten zuerst richtig gut ein bevor Du anfängst mit dem PHP-DI zu experimentieren. Das lernt mal normalerweise nicht mal so eben an 2 Nachmittagen
                              Naja, so kompliziert ist es nun aber auch nicht. Man muss eigentlich nur wissen, was dependency-injection ist, und warum man das new-Schlüsselwort sehr sparsam einsetzen sollte. Der Unterschied zwischen dependency-injection und einem Servicelocator sollte man auch kennen.

                              Generell kann man sagen, dass man den dependency-injection-container nur im Bootstrap oder in einer Factory wiederfinden kann. Im Rest der Applikation gibt es ihn nicht.
                              Standards - Best Practices - AwesomePHP - Guideline für WebApps

                              Kommentar

                              Lädt...
                              X