Alex_: Std::Class, Bezug soll aus Variable kommen

Hallo,

ich bekomme über eine API json-Daten, welche ich mit json-Decode innerhalb einer Klasse (API) umwandle und anschließend das erzeugte Objekt per Return zurückgebe. Bis hier hin funktioniert alles wie gewünscht und ich kann in der aufrufenden Testdatei auf das Objekt bzw. einzelne Werte des Objektes zugreifen und diese ausgeben (Beispiel 1).

Beispiel 1:

$api = new API();
$daten = $api->getDaten();       // get Object
echo $daten->benutzer->name;     // Testausgabe funktioniert

Beispiel 2:

$api = new API();
$daten $api->getDaten();         // get Object
$name = 'benutzer->name';         
$daten->$name;                   // Testausgabe funktioniert nicht

Im zweiten Beispiel soll nun über eine Variable auf die Objektdaten zugegriffen werden. Das Beispiel erzeugt aber folgenden Fehlercode:

Notice: Undefined property: stdClass::$benutzer->name in ...

Was mache ich falsch? Wie mache ich es richtig? Ist es möglich die Referenz auf Daten eines Objektes in einer Variablen zu speichern?

FG Alex

  1. Lieber Alex_,

    ich sehe da ein paar Ungereimtheiten in Deinem Code. Vielleicht kommt das aber auch von unvollständigem Copy&Paste?

    Beispiel 2:

    $api = new API();
    $daten $api->getDaten();         // get Object
    $name = 'benutzer->name';         
    $daten->$name;                   // Testausgabe funktioniert nicht
    

    [...] Notice: Undefined property: stdClass::$benutzer->name in ...

    Wo ist da das Istgleichzeichen bei $daten = $api->getDaten()? Das sollte eigentlich einen Syntaxfehler provozieren...

    Du willst $daten->{'benutzer'}->{'name'} haben, nicht jedoch $daten->{'benutzer->name'}! Da ich Dein Projekt jetzt nicht kenne, kann ich Dir nicht weiter raten. Vielleicht benötigst Du zwei Variablen wie hier:

    $daten = $api->getDaten();
    $subO  = 'benutzer';
    $prop  = 'name';
    
    echo $daten->$subO->$prop;
    

    Vielleicht aber auch willst Du über das Unterobjekt iterieren?

    $daten = $api->getDaten();
    $key   = 'benutzer';
    
    $subObj = $daten->$key;
    
    foreach (get_object_vars($subObj) as $prop => $val) {
        echo "subObj->$prop = '$val'\r\n"; // $val of type != object/class
    }
    

    Vielleicht nützt Dir das irgendwie. Code wie immer ungetestet!

    Liebe Grüße,

    Felix Riesterer.

    1. Lieber Felix,

      ich sehe da ein paar Ungereimtheiten in Deinem Code. Vielleicht kommt das aber auch von unvollständigem Copy&Paste?

      Die Ungereimtheiten waren wohl dem geschuldet, dass ich nicht Copy&Paste gemacht habe, sondern versucht habe ein fiktives einfaches Beispiel zu konstruieren. Sorry dafür. Sollte wohl um die Uhrzeit keine Beispiele mehr basteln

      Wo ist da das Istgleichzeichen bei $daten = $api->getDaten()? Das sollte eigentlich einen Syntaxfehler provozieren...

      Du hast vollkommen recht; und im Echtcode steht dort natürlich auch eins und es steht auch je ein echo vor den Testausgaben.

      Du willst $daten->{'benutzer'}->{'name'} haben, nicht jedoch $daten->{'benutzer->name'}! Da ich Dein Projekt jetzt nicht kenne, kann ich Dir nicht weiter raten. Vielleicht benötigst Du zwei Variablen wie hier:

      $daten = $api->getDaten();
      $subO  = 'benutzer';
      $prop  = 'name';
      
      echo $daten->$subO->$prop;
      

      Mit zwei (bzw. im Echtbeispiel dann tlw. drei) Variablen funktioniert es dann. Ich muss mal sehen, ob es mir dann tatsächlich noch weiter hilft/eine Vereinfachung bedeutet oder ob ich einen anderen Ansatz wählen sollte. Der Unterschied zwischen $daten->{'benutzer'}->{'name'} und $daten->{'benutzer->name'} wird mir im Zusammenhang mit der Antwort von Der-Dennis klar.

      Vielleicht aber auch willst Du über das Unterobjekt iterieren?

      Nein, dass ist hier nicht der Fall. Vielmehr will ich ähnliche Daten aus insgesamt 4 API´s eindeutig meinem Datenobjekt zuordnen; diese können aber in jeder API woanders stecken. Vlt. versuch ich mich nochmal an einem Beispiel. Ich hoffe sehr früh klappt das besser als sehr spät mit dem konstruieren eines Beispiels (der Echtcode würde wohl den Rahmen sprengen) :-)

      // API1
      $datenAPI1 = $getDaten('api1');         // Liefert ein Object mit vielen Daten
      $name = $datenAPI1->benutzer->name;     // An dieser Stelle befindet sich der Name im Object aus API1
      ### API 2 ###
      $datenAPI2 = $getDaten('api2');         // Liefert ein Object mit ähnlichen Daten
      $name = $datenAPI2->name                // Hier versteckt sich in API2 der Name
      
      

      Idee war, dass ich über eine Referenztabelle den "Speicherort" aus allen APIs auslese und die Daten so meinem Datenobjekt einfach zuordnen kann.

      bezeichnung | API1            | API2 | API3 | ...
      name          benutzer->name  | name | ...  | ...
      

      Vielleicht nützt Dir das irgendwie. Code wie immer ungetestet!

      Deine Antwort hilft mir sehr weiter, vielen Dank :)

      FG Alex

      1. Hallo Alex,

        Idee war, dass ich über eine Referenztabelle den "Speicherort" aus allen APIs auslese und die Daten so meinem Datenobjekt einfach zuordnen kann.

        bezeichnung | API1            | API2 | API3 | ...
        name          benutzer->name  | name | ...  | ...
        

        das ist doch schon mal eine viel bessere Beschreibung! Wie kommen die Daten denn in die Tabelle? Wenn die einigermaßen statisch ist, kannst Du das doch auch direkt in PHP machen. Z.B. (wenn Du OOP nutzt) durch eigene "Wrapper"-Klassen.

        Beispiel (auch ungetestet, geht ja nur um's Prinzip):

        abstract class ApiValues
        {
            protected $_valueObj = null;
        
            public function __construct(stdObj $valueObj)
            {
                $this->_valueObj = $valueObj;
            }
        
            public abstract function getName();
        }
        
        class Api1 extends ApiValues
        {
            public function getName()
            {
                return $this->_valueObj->benutzer->name;
            }
        }
        
        class Api2 extends ApiValues
        {
            public function getName()
            {
                return $this->_valueObj->name;
            }
        }
        
        // Verwendung
        
        $apiType = 'Api1';
        $parsedJson = $deinGeparstesJsonObjekt;
        
        switch ($apiType) {
            case 'Api1':
                $apiValues = new Api1($parsedJson);
                break;
            case 'Api2':
                $apiValues = new Api2($parsedJson);
                break;
        }
        
        $name = $apiValues->getName();
        

        Ist zwar was mehr Arbeit, so hast Du es aber übersichtlich, sauber und einen einfachen Zugriff auf die Werte. Musst Du natürlich auch nicht in einem Objekt machen, das geht grundsätzlich auch mit einfachen if-else- oder switch-Anweisungen, die dürften aber schnell relativ unübersichtlich werden.

        Gruß, Dennis

        1. Hallo Dennis!

          das ist doch schon mal eine viel bessere Beschreibung! Wie kommen die Daten denn in die Tabelle? Wenn die einigermaßen statisch ist, kannst Du das doch auch direkt in PHP machen. Z.B. (wenn Du OOP nutzt) durch eigene "Wrapper"-Klassen.

          Die Referenztabelle fülle ich manuell (spääter mal über ein Formular, aber immer noch manuell). Ich schaue mir also die API an und suche mir die benötigten Werte raus und speichere die "Referenz" bzw. war so der Plan.

          Beispiel (auch ungetestet, geht ja nur um's Prinzip):

          abstract class ApiValues
          {
              protected $_valueObj = null;
          
              public function __construct(stdObj $valueObj)
              {
                  $this->_valueObj = $valueObj;
              }
          
              public abstract function getName();
          }
          
          class Api1 extends ApiValues
          {
              public function getName()
              {
                  return $this->_valueObj->benutzer->name;
              }
          }
          
          class Api2 extends ApiValues
          {
              public function getName()
              {
                  return $this->_valueObj->name;
              }
          }
          
          // Verwendung
          
          $apiType = 'Api1';
          $parsedJson = $deinGeparstesJsonObjekt;
          
          switch ($apiType) {
              case 'Api1':
                  $apiValues = new Api1($parsedJson);
                  break;
              case 'Api2':
                  $apiValues = new Api2($parsedJson);
                  break;
          }
          
          $name = $apiValues->getName();
          

          Ist zwar was mehr Arbeit, so hast Du es aber übersichtlich, sauber und einen einfachen Zugriff auf die Werte. Musst Du natürlich auch nicht in einem Objekt machen, das geht grundsätzlich auch mit einfachen if-else- oder switch-Anweisungen, die dürften aber schnell relativ unübersichtlich werden.

          Vielen Dank für das Beispiel. Ich glaube jetzt hab ich endlich mal ein Anwendungsbeispiel mit Vererbung. Dieser Weg erscheint mir sehr sinnvoll und strukturiert; werde mich auf jeden Fall daran versuchen.

          Vielen Dank, für deine Antworten und die Codebeispiele. Ich glaube jetzt habe ich wieder eine Orientierung :)

        2. Eine Frage noch; vlt. ist es ja auch unwichtig oder nur eine persönliche Vorliebe von dir, aber folgt der Unterstrich vor dem Datenobjekt einer bestimmten Konvention oder ist der üblich?

           protected $_valueObj = null;
          

          Edit: Sorry, komme gerade auf dem Smartphone mit der Quellcode und Zitatfunktion nicht so klar :-/

          1. Tach!

            Eine Frage noch; vlt. ist es ja auch unwichtig oder nur eine persönliche Vorliebe von dir, aber folgt der Unterstrich vor dem Datenobjekt einer bestimmten Konvention oder ist der üblich?

            Der Unterstrich ist eines der erlaubten Zeichen, aus denen Bezeichner bestehen können. Man nimmt ihn gern mal, um private Bezeichner zu kennzeichnen oder um einem Konflikt zwischen privaten und öffentlichen Bezeichnern aus dem Weg zu gehen.

            Edit: Sorry, komme gerade auf dem Smartphone mit der Quellcode und Zitatfunktion nicht so klar :-/

            Leerzeilen vor dem einleitenden und nach abschließenden ~~~ müssen sein, genauso wie bei allen anderen Dingen, die einen Absatz erzeugen sollen.

            dedlfix.

            1. Hallo Dennis, hallo dedlfix,

              vielen Dank für eure Ausführungen. Ist ja meist am Anfang noch recht einfach sich etwas gleich anzugewöhnen, als sich später ab- bzw. umzugewöhnen.

              Habe auch gerade den Thread mit den "unaufgeräumten / aufgeblähten Code" entdeckt. Werde mir die dort verlinkten Konventionen mal bookmarken.

          2. Hallo Alex,

            Eine Frage noch; vlt. ist es ja auch unwichtig oder nur eine persönliche Vorliebe von dir, aber folgt der Unterstrich vor dem Datenobjekt einer bestimmten Konvention oder ist der üblich?

             protected $_valueObj = null;
            

            ergänzend zu dedlfix: Lass den Unterstrich einfach weg. Wenn ich mal eben schnell was schreibe, schreibe ich den aus Gewohnheit bei als protected oder private gekennzeichneten Methoden und Eigenschaften immer noch hin, weil das einige große Frameworks früher so gemacht haben. Ist aber eigentlich ziemlich sinnlos und wird nach neueren Konventionen auch nicht mehr gemacht.

            Gruß, Dennis

  2. Hallo Alex,

    Beispiel 2:

    $api = new API();
    $daten $api->getDaten();         // get Object
    $name = 'benutzer->name';         
    $daten->$name;                   // Testausgabe funktioniert nicht
    

    Im zweiten Beispiel soll nun über eine Variable auf die Objektdaten zugegriffen werden. Das Beispiel erzeugt aber folgenden Fehlercode:

    Notice: Undefined property: stdClass::$benutzer->name in ...

    Was mache ich falsch? Wie mache ich es richtig? Ist es möglich die Referenz auf Daten eines Objektes in einer Variablen zu speichern?

    zuerst einmal handelt es sich bei -> um einen Operator, Du verwendest ihn aber als eine Art Bezeichner, das sind zwei verschiedene Dinge. Den Unterschied solltest Du Dir zuerst einmal klar machen.

    Über stdClass kannst Du glaube ich nicht direkt iterieren. Du könntest das aber mit Funktionen wie get_object_vars() und evtl. Rekursion nachbauen. Kannst Du Dir ja mal ansehen.

    Ansonsten gibt es in PHP grundsätzlich noch zwei weitere Möglichkeiten, auf die Du früher oder später wohl stoßen wirst: Ich sage aber schon mal - und das ist wirklich wichtig - dass die nur mit großer Vorsicht verwendet werden sollten, wenn überhaupt, und auch nur, wenn Du genau weißt, was Du das da tust. Frage im Zweifel nochmal nach.

    1. Variable Variablen: Damit kannst Du den Inhalt einer Variablen als Bezeichner einer anderen Variablen verwenden.
    2. eval(): Einen String als PHP-Code auswerten.

    Nochmal, das ist nicht empfehlenswert, wenn man nicht ganz genau weiß, was man tut. Und selbst dann häufig nicht.

    Es wäre aber wohl nicht schlecht, wenn Du noch etwas mehr dazu sagen könntest, was Du vorhast, was Du Dir davon versprichst und in welchem Kontext das stattfindet. Je nachdem könnte es noch einige andere Punkte und Lösungen geben.

    Gruß, Dennis

    1. Hallo Dennis,

      vielen Dank für deine Antwort :)

      zuerst einmal handelt es sich bei -> um einen Operator, Du verwendest ihn aber als eine Art Bezeichner, das sind zwei verschiedene Dinge. Den Unterschied solltest Du Dir zuerst einmal klar machen.

      Das ist mir durch eure Antworten klar geworden. Bin vom Prinzip bei Arrays schonmal in die gleiche Falle getappt und brauchte erst wieder einen Wink, um das wieder parat zu haben.

      Über stdClass kannst Du glaube ich nicht direkt iterieren. Du könntest das aber mit Funktionen wie get_object_vars() und evtl. Rekursion nachbauen. Kannst Du Dir ja mal ansehen.

      Da ich nicht alle Variablen benötige, sondern nur einen kleinen Auszug hilft mir die Iteration hier glaube ich nicht weiter.

      Ansonsten gibt es in PHP grundsätzlich noch zwei weitere Möglichkeiten, auf die Du früher oder später wohl stoßen wirst: Ich sage aber schon mal - und das ist wirklich wichtig - dass die nur mit großer Vorsicht verwendet werden sollten, wenn überhaupt, und auch nur, wenn Du genau weißt, was Du das da tust. Frage im Zweifel nochmal nach.

      1. Variable Variablen: Damit kannst Du den Inhalt einer Variablen als Bezeichner einer anderen Variablen verwenden.
      2. eval(): Einen String als PHP-Code auswerten.

      Nochmal, das ist nicht empfehlenswert, wenn man nicht ganz genau weiß, was man tut. Und selbst dann häufig nicht.

      Habe (auch aufgrund deiner Warnung) die Dokumentation nur überflogen, um da jetzt auch nicht auf die falsche Fährte gelockt zu werden. Ich versuche auf diese Funktionen erstmal zu verzichten. Im Regelfall weiß ich nämlich nicht, was ich tue ;-)

      Es wäre aber wohl nicht schlecht, wenn Du noch etwas mehr dazu sagen könntest, was Du vorhast, was Du Dir davon versprichst und in welchem Kontext das stattfindet. Je nachdem könnte es noch einige andere Punkte und Lösungen geben.

      Ich bin in der Antwort an Felix etwas auf den Kontext eingegangen. Leider bin ich noch ganz in den Anfängen und kann leider nicht ins Detail gehen. Wollte es mit dem Beispielen möglichst einfach halten und auf das konkrete Problem bezogen. Dadurch konnte ich natürlich keinen Hilfe zu anderen Ansätzen oder Lösungswegen erwarten. Ich werde mich aber ggf. bei kommenden Fragen näher am Echtcode und weniger an Beispielen zu orientieren und auch etwas zu den Hintergründen zu schreiben.

  3. Hallo nochmal,

    ich habe nun mit eurer Hilfe die erste API-Klasse fertig.

    Bei der nächsten API bekomme ich nun leider Daten wo ich auf eine Variable mit dem Namen 'global' (wie das Schlüsselwort) zurückgreifen muss.

    Meine Versuche:

    $wert = $ergebnis->global->datum;
    $wert = $ergebnis->{'global'}->datum;
    

    Beide Varianten quittiert mir PHP mit einer ähnlichen Fehlermeldung wie in meinem Ausgangsthread:

    Notice: Undefined property: stdClass::$global in ...
    

    Ist dies nun ein Ausnahmefall wie von Dennis geschildert und verlinkt oder gibt es eine einfachere Lösung. Also das ich quasi das 'global' auf irgend eine Art escape.

    FG Alex

    1. Hat sich erledigt.

      Der Fehler lag an anderer Stelle. Nicht das vermutete 'global' war das Problem, sondern dass ich eine "Stufe" übersehen hatte.

      Richtig war: $wert = $ergebnis->daten->global->datum;

    2. Tach!

      Notice: Undefined property: stdClass::$global in ...

      Du hast den Fehler ja schon gefunden. Bei Fehlern dieser Art helfen Kontrollausgaben: print_r($ergebnis) oder var_dump($ergebnis). Damit sollte man sehen, dass es das nicht gibt, worauf man zuzugreifen versucht (außer wenn die Magic Methods ins Spiel kommen).

      dedlfix.

      1. Du hast den Fehler ja schon gefunden. Bei Fehlern dieser Art helfen Kontrollausgaben: print_r($ergebnis) oder var_dump($ergebnis). Damit sollte man sehen, dass es das nicht gibt, worauf man zuzugreifen versucht (außer wenn die Magic Methods ins Spiel kommen).

        Das ist ein guter Tipp. So habe ich letztlich auch den Fehler gefunden. Hatte zunächst die unbearbeiteten API-Daten mit print_r($ergebnis) ausgegeben und so die für mich relevanten Variablen rausgesucht. Problem war, dass die Daten aus der API sehr umfangreich und tief verschachtelt sind und ich daher die eine Stufe übersehen habe. Da ich den Fehler in dem 'global' vermutet hatte, hätte mich hier wohl auch ein print_r($ergebnis->global->datum); nicht weitergebracht; da ich wieder vermutet hätte, dass es nur leer ist wegen dem global.

        Naja, nachdem ich die API-Daten dann mit echo '<pre>'; print_r($ergebnis); echo '</pre>';

        formatiert ausgegeben habe und ich mit der Erstellung des hiesigen Beitrages kurz aus den Wald heraus geschaut habe; hatte ich den Fehler dann doch recht schnell gefunden.

        Meine Fehlersuche läuft meist so ab:

        1.) Codezeilen aus der Fehlermeldung ansehen => geschätzte 50% der Fehler kann ich danach beheben

        2.) Nach der Fehlermeldung googeln => geschätzte weitere 20% der Fehler kann ich danach beheben

        3.) Testausgaben erstellen => geschätzte weitere 15%

        4.) Mal eben Emails checken und/oder ein bißchen surfen und dann wieder bei 1) anfangen => weitere 5%

        5.) Eine Frage hier im Forum formulieren ohne abzusenden => 5%

        6.) Die Frage absenden und mit eurer Hilfe eine Lösung finden => 5%;

        Die 5% aus 6) sollen natürlich die Leistung der Helfer hier schmälern; denn von den 5% werden nahezu 100% gelöst, so dass nie ein 7.) notwendig ist :)

        1. Tach!

          Meine Fehlersuche läuft meist so ab:

          1.) Codezeilen aus der Fehlermeldung ansehen => geschätzte 50% der Fehler kann ich danach beheben
          2.) Nach der Fehlermeldung googeln => geschätzte weitere 20% der Fehler kann ich danach beheben
          3.) Testausgaben erstellen => geschätzte weitere 15%
          4.) Mal eben Emails checken und/oder ein bißchen surfen und dann wieder bei 1) anfangen => weitere 5%
          5.) Eine Frage hier im Forum formulieren ohne abzusenden => 5%
          6.) Die Frage absenden und mit eurer Hilfe eine Lösung finden => 5%;

          Die 5% aus 6) sollen natürlich die Leistung der Helfer hier schmälern; denn von den 5% werden nahezu 100% gelöst, so dass nie ein 7.) notwendig ist :)

          Fragen der Kategorie 1 bis 3 sind auch langweilig. Ich begrüße das sehr, dass du so vorgehst und wünsche mir, dass das jeder so macht.

          dedlfix.

          1. Hallo,

            Fragen der Kategorie 1 bis 3 sind auch langweilig. Ich begrüße das sehr, dass du so vorgehst und wünsche mir, dass das jeder so macht.

            Während Kategorien 4 und 5 manchmal recht witzig/interessant/lehrreich sein können, vorallem die Frage "Warum hab ich das denn nicht gleich gesehen?"

            Gruß
            Kalk