ottogal: PHP: Variablennamen erzeugen?

Hallo in die Runde,

gibt es in PHP eine Möglichkeit aus einem String eine "gleichnamige" Variable, halt mit vorangestelltem $, zu erzeugen?

(Der String enthalte keine unzulässigen Zeichen, z.B. 'der_Baum'; daraus soll die Variable $der_Baum erzeugt werden.)

Der gegebene String selbst soll nicht nur als Konstante, sondern auch als Variable vorliegen können.

Freilich würde es mich erstaunen, wenn das möglich wäre...

  1. Hallo,

    gibt es in PHP eine Möglichkeit aus einem String eine "gleichnamige" Variable, halt mit vorangestelltem $, zu erzeugen?

    ja, aber wozu soll das gut sein?

    Freilich würde es mich erstaunen, wenn das möglich wäre...

    Da es PHP ist, sollte dich eigentlich nichts erstaunen.

    $str = 'Rudi';
    ${$str} = 'Heute herrscht Regenwetter.';
    echo $Rudi;             // -> 'Heute herrscht Regenwetter.'
    

    Aber nochmal: Wozu? Ich kann mir keinen Fall vorstellen, in dem das wirklich sinnvoll wäre. Andere Lösungen, etwa assoziative Arrays (d.h. Arrays mit alphanumerischen Schlüsseln) sind in aller Regel günstiger.

    So long,
     Martin

    --
    Nothing travels faster than the speed of light with the possible exception of bad news, which obeys its own special laws.
    - Douglas Adams, The Hitchhiker's Guide To The Galaxy
  2. Hallo

    gibt es in PHP eine Möglichkeit aus einem String eine "gleichnamige" Variable, halt mit vorangestelltem $, zu erzeugen?

    (Der String enthalte keine unzulässigen Zeichen, z.B. 'der_Baum'; daraus soll die Variable $der_Baum erzeugt werden.)

    $string = 'der_Baum';
    $$string = 'Neuer Wert von $der_Baum.';
    
    echo $der_Baum;  // Neuer Wert von $der_Baum.
    

    Freilich würde es mich erstaunen, wenn das möglich wäre...

    Das geht also. Ob das guter Stil ist, steht auf einem anderen Blatt.

    @Alle: Was ist einem Angreifer möglich, wenn er dem Skript Strings unterschieben kann?

    Tschö, Auge

    --
    Wo wir Mängel selbst aufdecken, kann sich kein Gegner einnisten.
    Wolfgang Schneidewind *prust*
    1. Tach!

      @Alle: Was ist einem Angreifer möglich, wenn er dem Skript Strings unterschieben kann?

      Überschreiben und/oder Auslesen anderer Variablen im Scope kann möglich sein. Was die Folge davon sein kann, hängt vom restlichen Code ab.

      dedlfix.

    2. Hallo,

      $str = 'Rudi';
      ${$str} = 'Heute herrscht Regenwetter.';
      echo $Rudi;             // -> 'Heute herrscht Regenwetter.'
      

      @Alle: Was ist einem Angreifer möglich, wenn er dem Skript Strings unterschieben kann?

      Er kann das Wetter beeinflussen?

      Gruß
      Kalk

  3. Tach!

    gibt es in PHP eine Möglichkeit aus einem String eine "gleichnamige" Variable, halt mit vorangestelltem $, zu erzeugen?

    Ja, das Konzept nennt sich "variable Variablen". Unter dem Begriff ist es im PHP-Handbuch zu finden.

    Nur, eigentlich will man solche eine Lösung nicht haben, es macht den Code in der Regel schwerer nachvollziehbar. Als Alternative könnte man statt der Variablen Einträge in einem Array nehmen. Damit umgeht man zumindest die "Verunreinigung" des Variablenscopes mit unbekannten und vielleicht kollidierenden Variablen. Das Prinzip der unbekannten Einträge bleibt aber bestehen, es ist nur etwas entschärft. Was ist nun der beste Ersatz? Nun, das kann ich ohne die eigentliche Aufgabenstellung zu kennen nicht beurteilen.

    dedlfix.

  4. Vielen Dank allen für die rasche Erhellung - ich glaube nun, über die variablen Variablen im Manual schon mal gestolpert zu sein...

    Wozu ich das brauche:

    Im Code (eines Templates für ein CMS - bitte fragt nicht nach dem Sinn...) wird immer wieder der Aufruf einer Funktion foo() nötig, die als Argument ein Array in dieser Form erwartet (und einen String zurückgibt):

    echo foo(array(
    	'eins' => $eins,
    	'zwei' => $zwei,
    	'drei' => $drei,
    	)) . "\n";
    
    

    Die Aufrufe dieser Funktion brauchen meist größere Arrays und längere Strings, haben jedoch immer diese Form. Um das Coden zu vereinfachen, wollte ich daher alternativ die folgende Funktion bar() verwenden, deren Aufruf nur eine einfache Liste von String-Argumenten erfordert - im Beispiel:

    bar('eins', 'zwei', 'drei'); vgl. dazu im Manual.

    function bar(){
        $numargs = func_num_args();
        $arg_list = func_get_args();
        $params = array[];
        for ($i = 0; $i < $numargs; $i++) {
    	$key =  $arg_list[$i];
    	$params[$key] =  ${$key} ;  // <<< hier fehlte mir die Kenntnis der Syntax ${$key}
        };
        echo foo($params) . "\n";
    }
    
    

    Noch habe ich diese Funktion nicht getestet, frage euch aber schon mal: Könnte es so klappen? Ließe sich der Code weiter vereinfachen?

    1. Mahlzeit,

      Im Code (eines Templates für ein CMS - bitte fragt nicht nach dem Sinn...) wird immer wieder der Aufruf einer Funktion foo() nötig, die als Argument ein Array in dieser Form erwartet (und einen String zurückgibt):

      echo foo(array(
      	'eins' => $eins,
      	'zwei' => $zwei,
      	'drei' => $drei,
      	)) . "\n";
      

      das sieht für mich doch einigermaßen vernünftig aus. Was gefällt dir daran nicht?

      Die Aufrufe dieser Funktion brauchen meist größere Arrays und längere Strings, haben jedoch immer diese Form. Um das Coden zu vereinfachen, wollte ich daher alternativ die folgende Funktion bar() verwenden, deren Aufruf nur eine einfache Liste von String-Argumenten erfordert

      Das hat einen Haken. Du musst eine gewisse Anzahl globale Variablen hegen und pflegen, die so heißen wie die Arrayeinträge, die foo() erwartet. Globale Variablen möchte man aber normalerweise so weit wie möglich vermeiden.

      bar('eins', 'zwei', 'drei'); vgl. dazu im Manual.

      function bar(){
          $numargs = func_num_args();
          $arg_list = func_get_args();
          $params = array[];
          for ($i = 0; $i < $numargs; $i++) {
      	$key =  $arg_list[$i];
      	$params[$key] =  ${$key} ;  // <<< hier fehlte mir die Kenntnis der Syntax ${$key}
      	                            // PROBLEM!!
          };
          echo foo($params) . "\n";
      }
      

      An der mit "PROBLEM" markierten Stelle greifst du aus der Funktion heraus auf globale Variablen zu. Das geht aber nicht so ohne weiteres; du müsstest sie eigentlich zu Beginn der Funktion mit dem Schlüsselwort global bekanntmachen, sozusagen in die Funktion importieren. Das geht aber auch nicht, wenn du ihre Namen nicht kennst.
      Du könntest stattdessen auf $_GLOBALS['name'] zugreifen, dann hätte sich das ganze Gezappel mit variablen Variablen erledigt. Schöner oder besser wird das Konzept davon aber nicht.

      Ich kenn den restlichen Code drumherum nicht. Aber vermutlich würde ich ein Array vorhalten, das die von foo() erwarteten Einträge besitzt, jeweils bedarfsweise die Felder dieses Arrays setzen (anstatt der einzelnen globalen Variablen), und dann das Array als Ganzes an foo() übergeben.

      So long,
       Martin

      --
      Nothing travels faster than the speed of light with the possible exception of bad news, which obeys its own special laws.
      - Douglas Adams, The Hitchhiker's Guide To The Galaxy
      1. An der mit "PROBLEM" markierten Stelle greifst du aus der Funktion heraus auf globale Variablen zu. Das geht aber nicht so ohne weiteres; du müsstest sie eigentlich zu Beginn der Funktion mit dem Schlüsselwort global bekanntmachen, sozusagen in die Funktion importieren. Das geht aber auch nicht, wenn du ihre Namen nicht kennst.

        Etwas allgemeiner ausgedrückt versucht er an der Stelle auf eine Variable des umschließenden Geltungsbereichs zuzugreifen, das muss nicht unbedingt auch der globale Geltungsbereich sein. Für den allgemeineren Fall kann man Variablen mit der use-Klausel von den äußeren in den inneren Geltungsbereich importieren:

        $foo = 42;
        function bar () use ($foo) {
           return $foo;
        }
        bar(); // 42
        

        Für die Funktion bar behält der PHP-Jit-Compiler nun eine Referenz auf die Variable $foo. Das ist wichtig, weil sonst die Garbage-Collection die Variable aufräumen könnte und die Funktion damit undefiniert wäre. Das ist das Konzept eines Closures. Würde man VVs in dem Closure zulassen, müsste PHP für jede Variable des äußeren Scopes eine Referenz verwalten und die Garbage-Collection wäre gelähmt - das wäre ziemlich blöd.

        1. Tach!

          Für den allgemeineren Fall kann man Variablen mit der use-Klausel von den äußeren in den inneren Geltungsbereich importieren:

          $foo = 42;
          function bar () use ($foo) {
             return $foo;
          }
          bar(); // 42
          

          Das geht so aber nicht. Die Funktion muss anonym sein, damit man use verwenden kann.

          $bar = function() use ($foo) {...};
          

          dedlfix.

          1. Sehr lehrreich, danke euch!

    2. Tach!

      Im Code (eines Templates für ein CMS - bitte fragt nicht nach dem Sinn...) wird immer wieder der Aufruf einer Funktion foo() nötig, die als Argument ein Array in dieser Form erwartet (und einen String zurückgibt):

      Damit sind wir schon einen Schritt weiter, aber noch nicht an dem Punkt, an dem wir meines Erachtens eine Alternative zu VV nehmen können. Sicherheitstechnisch spricht da im Prinzip auch nichts dagegen, solange das alles fest kodiert ist und Anwender die Namen 'eins', 'zwei', et cetera nicht beeinflussen können.

      function bar(){
          $numargs = func_num_args();
          $arg_list = func_get_args();
          $params = array[];
          for ($i = 0; $i < $numargs; $i++) {
      	$key =  $arg_list[$i];
      	$params[$key] =  ${$key} ;  // <<< hier fehlte mir die Kenntnis der Syntax ${$key}
          };
          echo foo($params) . "\n";
      }
      
      

      Könnte es so klappen?

      Sieht so aus.

      Ließe sich der Code weiter vereinfachen?

      Ja. Zum einen existiert foreach, zum anderen gibts ab PHP 5.6 die ...-Syntax

      function bar(...$arg_list) { 
        $params = [];
        foreach($arg_list as $arg) { 
          $params[$arg] = $$arg; // oder ${$arg}
        }
        echo foo($params);
      }
      

      Man könnte auch funktional die Sache lösen, unter Verwendung von array_map(), aber das macht es in dem Fall nicht einfacher.

      dedlfix.

      1. Tach!

        Könnte es so klappen?

        Sieht so aus.

        Sah aber leider nur so aus. Das Problem daran ist, dass die Variablen nicht im aktuellen Scope zu finden sind. Sie liegen im Scope, wenn du foo() direkt aufrufst, aber bar() hat seinen eigenen Scope. Damit musst du sie entweder reinreichen, aber dann kannst du gleich foo() verwenden, oder sie irgendwo holen gehen, und das wäre im globalen Scope mittels $GLOBALS['name'] oder aus einem Array, das irgendwo global rumliegt $GLOBALS['arrayname']['name']. Nicht schön, aber wenn die Variablen eh schon im globalen Scope rumliegen, wirds auch nicht mehr schlimmer.

        Ließe sich der Code weiter vereinfachen?

        Also nicht VV verwenden, sondern auf $GLOBALS-Einträge zugreifen.

        dedlfix.

        1. Hallo,

          Könnte es so klappen?

          Sieht so aus.

          Sah aber leider nur so aus. Das Problem daran ist, dass die Variablen nicht im aktuellen Scope zu finden sind.

          what I said. ;-)

          Also nicht VV verwenden, sondern auf $GLOBALS-Einträge zugreifen.

          Oder auf diesen Holzw... äh, Umweg ganz verzichten.

          Ciao,
           Martin

          --
          Nothing travels faster than the speed of light with the possible exception of bad news, which obeys its own special laws.
          - Douglas Adams, The Hitchhiker's Guide To The Galaxy
    3. Das Problem mit dem Scope der Variablen hatte ich allerdings nicht bedacht.

      foreach ist freilich viel eleganter als die for-Schleife, zu der man erst die Anzahl ermitteln muss. Ganz neu war mir die ...-Syntax (hab sie dann hier gefunden).

      Danke nochmal für die hilfreichen Erläuterungen!

    4. Ein weiterer Gedanke:

      Der Scope der Variablen wäre ja kein Problem, wenn sie als Funktions-Argumente übergeben würden (oder irre ich mich?); der Aufruf wäre dann ebenso schön kurz: bar($eins, $zwei, $drei).

      function bar(...$arg_list) { 
        $params = [];
        foreach($arg_list as $arg) {
          $key = ????($arg);         // <<< soll z.B. $eins in 'eins' verwandeln
          $params[$key] = $arg;
        }
        echo foo($params);
      }
      
      

      Gibts denn in PHP auch eine Möglichkeit, aus dem Variablennamen $eins den String 'eins' zu gewinnen?

      1. Tach!

        Der Scope der Variablen wäre ja kein Problem, wenn sie als Funktions-Argumente übergeben würden (oder irre ich mich?); der Aufruf wäre dann ebenso schön kurz: bar($eins, $zwei, $drei).

        Ja, als Parameter gelangen die Werte in den Scope der Funktion, weil sie nun in $arg_list stehen.

        Gibts denn in PHP auch eine Möglichkeit, aus dem Variablennamen $eins den String 'eins' zu gewinnen?

        Diese Variablennamen existieren nicht im inneren Scope. PHP übergibt nicht die Variablen, sondern die Werte, auf die die Variablen verweisen. Man erklärt Variablen gern als Schublade, in der was drin ist. Aber das Bild passt nicht so recht zur Wirklichkeit. Vielmehr ist eine Variable nur eine Karteikarte, auf der geschrieben steht, an welcher Position im Lager der Inhalt zu finden ist. Du hast nun zwei Karteikarten, eine im Scope, aus dem du die Funktion aufrufst, und eine im Scope der Funktion.

        Man kann zwar mit Reflection Dinge im Code untersuchen, aber du müsstes dabei nicht die Funktion sondern den Funktionsaufruf untersuchen. Und ob letzteres möglich ist, weiß ich nicht. Das sieht mir auf den ersten Blick auch nicht so aus.

        dedlfix.

        1. Interessant! Aber Reflection wäre dann doch eine etwas dicke Kanone für meine kleinen Spatzen...