Ankündigung

Einklappen
Keine Ankündigung bisher.

MeineKlassenListe<KlassenName>()

Einklappen

Neue Werbung 2019

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

  • MeineKlassenListe<KlassenName>()

    Hallo,

    ich hab mal wieder ein kleines schönes Standardprojekt. Neues sauberes Datenbankmodell mit überschaubaren ~10 Entitäten plus Beziehungen untereinander.

    Ich ziehe das ganze mit dem Zend Framework auf und mache es immer so, dass ich mir selbst OR-Mapper schreibe:

    datenbank.tabelle (mit z.B. id, name, ..)
    class Tabelle {
    setId(), getId(),
    setName(), getName(),
    fromArray(), toArray()
    }
    class Tabelle_List extends ArrayObject (bildet einen Array dieser Objekte ab)
    class Tabelle_Model_Abstract
    class Tabelle_Model_Database
    class Tabelle_Model_Interface
    class Tabelle_Form_Add extends Zend_Form
    class Tabelle_Form_Edit extends Tabelle_Form_Add

    (mal etwas vereinfacht)
    Jetzt geht mir die blöde Tipperei auf die Nerven, das für alle 10 Entitäten zu erstellen, da eigentlich alle dasselbe machen (und jetzt schon dabei bin, Code zu kopieren und Namen zu ersetzen). Ich möchte aber keine generischen Objekte ala
    $abstract->setProperty("id", $id)
    weil ich dieses total generische schwer zu debuggen finde und ein Workaround für Ausnahmen schlecht einzufügen ist (overwrite setProperty mit switch-case). Ein Freund magischer Methoden bin ich auch nicht, da machen die wenigsten Auto-Complete-Funktionen der IDEs nicht mit, es Performance-Vorbehalte gibt und es außerdem vielleicht deprecated in PHP 6 ist, wer weiß.

    Einen Generator anwerfen finde ich auch doof, weil ich nämlich einen selber schreiben müsste und bei meinen Ansprüchen dauert das wieder ne Woche.

    Jetzt kam mir gerade diese blöde aber ziemlich fix funktionierende Lösung:
    PHP-Code:
    <?php
    abstract class ObjectList
    {
        public static function 
    build($class)
        {
            if (!
    class_exists($class)) {
                throw new 
    Exception("class [$class] not found");
            }
            
    $listClass $class "List";
            if (
    class_exists($listClassfalse)) {
                return new 
    $listClass();
            }
            
    $php self::_generateListClass($listClass$class);
            
    $tempFile tempnam(sys_get_temp_dir(), "php");
            
    file_put_contents($tempFile$php);
            include 
    $tempFile;
            
    unlink($tempFile);
            return new 
    $listClass;
        }
        
        protected static function 
    _generateListClass($listClass$singleClass)
        {
            
    // nowdoc syntax:
            
    $php = <<<'PHP_CLASS_DECLARATION'
    <?php
    class %__listClass__% extends ArrayObject
    {
        public function offsetSet($offset, $element)
        {
            if (!$element instanceof %__singleClass__%) {
                $type = gettype($element);
                if ($type === "object") {
                    $type .= "::" . get_class($element);
                }
                throw new InvalidArgumentException("invalid element provided [$type], must be an instance of %__singleClass__%");
            }
            if (!($offset === null || (ctype_digit((string)$offset) && $offset >= 0))) {
                throw new InvalidArgumentException("invalid element offset provided, must be NULL or unsigned integer");
            }
            return parent::offsetSet($offset, $element);
        }
        
        public function append($element)
        {
            return $this->offsetSet(null, $element);
        }
    }

    PHP_CLASS_DECLARATION;
            
    $php strtr($php, array(
                
    "%__listClass__%" => $listClass,
                
    "%__singleClass__%" => $singleClass
            
    ));
            return 
    $php;
        }
    }

    class 
    Test
    {
      public function 
    getName() {
        return 
    spl_object_hash($this);
      }
    }

    class 
    NotTest
    {
        
    }

    $testList ObjectList::build("Test");
    $testList ObjectList::build("Test");
    $testList[] = new Test();
    $testList[] = new Test();
    foreach (
    $testList as $test) {
      echo 
    $test->getName(), "<br />";
    }

    $testList[] = new NotTest(); // Exception
    Irgendwie auch nicht so das wahre, aber es funktioniert. Ich weiß, ich sollte den Skill haben zu wissen, dass das Code auf eval()-Niveau ist. Zugegeben es ist auch nur das Ziel erreicht, dass ich Tabelle_List erstellt habe und damit sicherstelle, dass in Tabelle_List auch nur Objekte vom Typ Tabelle stehen habe. Aber immerhin. Wie gesagt, ich könnte auch 10x (oder bei anderen Projekte 50x) eine <Datenbanktabelle>_List erstellen, aber es ärgert mich irgendwie, dass es kein MeineKlasse<Klasse>() Konstrukte wie in Java gibt und ich würde das gerne nachbauen.

    Gibt es denn eine PHP-Extension, die soetwas wie MeineKlassenListe<KlassenName>() unterstützt?

    Wie macht ihr das, wenn ihr soviel Gleiches zu tun habt, Doctrine benutzen?

  • #2
    Einen Generator anwerfen finde ich auch doof, weil ich nämlich einen selber schreiben müsste und bei meinen Ansprüchen dauert das wieder ne Woche.
    Naja, im Prinzip hast du damit doch genau das gemacht, einen Generator geschrieben. Auch wenn der on-the-fly arbeitet und die Datei direkt wieder löscht (warum eigentlich? Wäre doch nett, das zumindest zu cachen). Was ich bisher an Lösungen für Generics in PHP gesehen habe, läuft auch alles auf das selbe Prinzip hinaus.

    Was ich gerne mal mache, wenn ich viele Klassen mit gleichartigem Boilerplate Code brauche, der sich nicht vermeiden lässt, ist Generierung mittels Ant-Skript: Ich erstelle eine Template-Datei und ein dynamisches Filterset, fertig ist der Generator.

    Ein Beispiel, gekürzt:

    Klassen-Template:
    PHP-Code:
    class {vendor}_{extension}_Model_Entity_{model}_Collection
        
    extends Mage_Core_Model_Mysql4_Collection_Abstract
    {
        
    /**
         * standard collection feature
         * 
         * @param {vendor}_{extension}_Model_{model} ${model_l}
         * @param boolean $strict true for a strict comparison (same object), false
         *  for a by-attribute comparison. Defaults to true
         * @return boolean
         */
        
    public function contains(
            {
    vendor}_{extension}_Model_{model} ${model_l}, $strict true)
        {
            foreach (
    $this->getIterator() as $item) {
                if (
    $strict
                    
    ? ($item === ${model_l})
                    : (
    $item->getData() == ${model_l}->getData())
                ) {
                    return 
    true;
                }
            }
            return 
    false;
        }
        
    // ...

    Ant-Code:
    Code:
        <target name="create-model-collection" depends="define-properties" if="model-name">
            <stringutil string="${model-name}" property="model-path">
                <replace regex="_" replacement="/"></replace>
            </stringutil>
            <copy tofile="${extension-code-dir}/Model/Entity/${model-path}/Collection.php" file="template/classes/Collection.php">
                <filterset refid="template-filter" />
            </copy>
        </target>
    
        <target name="define-properties" if="extension-name">
            <!-- ... -->
            <stringutil string="${model-name}" property="model-lc">
                <lowercase/>
            </stringutil>
            <filterset id="template-filter" begintoken="{" endtoken="}">
                <filter token="vendor" value="${extension-vendor}" />
                <filter token="extension" value="${extension-name}" />
                <filter token="model" value="${model-name}" />
                <filter token="model_l" value="${model-lc}" />
                <!-- ... -->
            </filterset>
        </target>
    Die Properties ergeben sich teilweise aus der Konfiguration, teilweise aus Runtime-Parametern, und werden von verschiedenen Klassengeneratoren gemeinsam genutzt.

    Kommentar


    • #3
      Hm mit Ant ist natürlich auch ne tolle Idee, genauso wenig bin ich auf Templates gekommen (warum eigentlich? - ist ja naheligend).

      Was ich ja super genial fände, wenn endlich Traits kämen. die könnte ich gerade gut gebrauchen. Wobei ich grade 10 RFCs offen habe, die - unabhängig von der Problematik hier im Thread - echt interessant klingen.

      Ich glaube ich nehme deinen Vorschlag mit Templates mal auf. Abgesehen davon ist mir aufgefallen, dass ich ja nur eine Datei ObjectList bräuchte, die die Implementierung oben beinhaltet. Irgendwie zu verquer gedacht mal wieder.

      Edit: Ich könnte ja für die Templates schonmal Trait-Syntax verwenden ..

      Kommentar


      • #4
        Die Traits sind längst da
        http://www.php.net/archive/2011.php#id2011-06-28-1
        gut, alpha, aber immerhin.

        Kommentar


        • #5
          Traits erlauben nur leider keine Platzhalter in der Form.

          Kommentar


          • #6
            ... dann sollte sich endlich jemand Generics für PHP wünschen!

            Kommentar


            • #7
              Ich hab da zuwenig Grundkenntnisse in dem Bereich, mach du doch mal?!

              Kommentar


              • #8
                Bitte nicht

                Kommentar


                • #9
                  Warum denn nicht? Ein

                  PHP-Code:
                  $foo = new ArrayList<User>(); 
                  wäre hinsichtlich der Typensicherheit schon nicht das schlechteste. Was aber viel dringender - aber auch in diesem Zusammenhang - schön wäre ist die Deklaration von Rückgabe-Werten. Hast du eine Methode Group::getUsers(), so könnte diese "in schön" vielleicht bald so aussehen:

                  PHP-Code:
                  class Group {

                     public function List<
                  UsergetUsers(){
                     }


                  Sofern das die IDEs noch entsprechend unterstützen, braucht es zukünftig keine expliziten Type-Hints mehr. Für mich eine echte Erleichterung, denn die bisherige Typ-Deklaration von Typen wie "array" ist nicht wirklich schön.

                  Kommentar


                  • #10
                    ich wär da mehr für sowas:

                    PHP-Code:
                    class Group {


                       ( boolean ) function 
                    Method() {

                       }


                    alternativ:

                    PHP-Code:
                    prototype pGroup {
                       function 
                    Method() as boolean;
                    }

                    class 
                    Group satisfy pGroup {

                       function 
                    Method() {

                       }


                    Wenn wir schon mal beim Wünschen wären, würde ich gerne zusätzlich zum Type-hinting, Type-autocasting machne dürfen:
                    PHP-Code:
                    class Group {

                       function 
                    Method( (string) existingClass $arg1 ) {

                       }


                    alternativ:

                    PHP-Code:
                    prototype pGroup {
                       function 
                    Methodstring $arg1 );

                    i-win-situation:

                    PHP-Code:
                    prototype pGroup {
                       function 
                    Methodstring $arg1 ) as boolean;
                    }

                    class 
                    Group
                       satisfy pGroup
                       
                    implements iGroupCountable
                    {

                       function 
                    MethodexistingClass $arg1 ) {

                       }


                    Coming soon, PHP 7.. or later

                    Kommentar


                    • #11
                      Bitte nicht
                      Lasst sowas lieber IDEs machen. PHP soll bitte untypisiert bleiben. Das ist cool so, wie es ist. Find ich zumindest. Naja, Philosophiefrage.

                      Kommentar


                      • #12
                        Aber ja, es sollte optional sein... Mich bei jeder Funktion auf den Rückgabe-Typ festzulegen ist auf dauer bissl C.

                        Kommentar


                        • #13
                          hach, da bekomm ich Lust, mal wieder den gcc auszupacken.

                          Ich finde es nicht gut, mit (sanfter) Gewalt zu versuchen, Merkmale verschiedener Sprachen zu mischen. Und wenn man Templates in C++ bzw. Generics in Java/C# ansieht, ist das mehr als nur eine kleine Erweiterung. Wenn man wirklich mal an den Punkt komme, wo sowas nötig sein sollte, sollte man darüber nachdenken, die Sprache zu wechseln. Auch mit C++ lassen sich Webprojekte entwickeln

                          Kommentar


                          • #14
                            Zitat von dr.e. Beitrag anzeigen
                            Warum denn nicht? Ein

                            PHP-Code:
                            $foo = new ArrayList<User>(); 
                            wäre hinsichtlich der Typensicherheit schon nicht das schlechteste. Was aber viel dringender - aber auch in diesem Zusammenhang - schön wäre ist die Deklaration von Rückgabe-Werten. Hast du eine Methode Group::getUsers(), so könnte diese "in schön" vielleicht bald so aussehen: ...
                            Generiks brauche ich net umbedingt, aber Rückgabewerte und TypeGinting für Strings und int/float fände ich super, da weiß man dann auch was rein- und wieder raus- kommt...

                            Kommentar


                            • #15
                              @zwutz: Auto-typecasting ist ja jetzt nicht die Gewalt pro natura. Das machste ja jetzt innerhalb der funktion..

                              jetzt:
                              PHP-Code:
                              function method $str_arg ) {
                                 
                              $arg = (string) $str_arg;

                              schön:
                              PHP-Code:
                              function method ( (string) $arg ) {


                              Return-Types fänd ich relativ toll, allein schon aus analytischen Gründen..

                              PHP-Code:
                              function method () as boolean {
                                 return 
                              true;
                              }

                              function 
                              method2 () as integer {
                                 return 
                              true;
                              }

                              function 
                              method3 () as (integer) {
                                 return 
                              true;

                              method2 => fatal error: method2 tries to return an illegale type.

                              PHP-Code:
                              function method () as boolean {
                                 return 
                              true;
                              }

                              $o get_return_type('method'); // string "boolean"

                              $o has_boolean_return('method'); // boolean true
                              $o has_integer_return('method'); // boolean false

                              class foo {

                                 function 
                              bar() as (boolean) {
                                    return 
                              true;
                                 }

                              }

                              $o get_return_type(array(new foo'bar')); // string '(integer)'
                              $o has_boolean_return(array(new foo'bar')); // boolean false
                              $o has_integer_return(array(new foo'bar')); // boolean true

                              class baz {

                                 function 
                              bar( (integer) $value ) as (boolean) {
                                    return 
                              $value;
                                 }

                              }

                              $o = new baz;
                              $r $o->bar('1.00000'); // boolean true;

                              $n get_func_cast_type(array($o'bar'), 0); // string "integer"

                              // ... 

                              Kommentar

                              Lädt...
                              X