Linuchs: Array als PHP-snipped in Datei schreiben und lesen

problematische Seite

Moin,

für die Termintypen von Veanstaltungsdaten erzeuge ich ein 2-dimensionales Array, das sich monatelang nicht ändert. Also möchte ich es nicht bei jedem Programmlauf per SQL und PHP neu erzeugen (0.04900s), sondern einmalig in eine Datei schreiben und die Datei als PHP-Code lesen (0.00000s).

Obwohl file_put_contents ein array zulässt, kann ich es von Datei mit file_get_contents nicht wieder herstellen:

echo "<pre class=nodisplay>arr_termintypen:\n\n";
var_dump( $arr_termintypen );
echo "</pre>\n";

file_put_contents( "cache/arr_termintypen.php", $arr_termintypen );

$test = file_get_contents( "cache/arr_termintypen.php" );

echo "<pre class=nodisplay>test:\n\n";
var_dump( $test );
echo "</pre>\n";

Ergebnis:

<pre class=nodisplay>arr_termintypen:

array(44) {
  [24]=>
  array(12) {
    ["TYP"]=>
    string(2) "24"
    ["anzahl_termine"]=>
    string(1) "0"
    ["typgruppe_id"]=>
    string(1) "1"
    ["color"]=>
    string(7) "#f2004c"
    ["sort"]=>
    string(7) "bil_fue"
    ["typ_beschreibung"]=>
    string(0) ""
    ["gruppe_de"]=>
    string(7) "Bildung"
    ["name_de"]=>
    string(23) "Führung, Bildungsreise"
    ["gruppe_en"]=>
    string(9) "Education"
    ["name_en"]=>
    string(32) "Guided tour, Educational journey"
    ["gruppe_nl"]=>
    string(9) "Onderwijs"
    ["name_nl"]=>
    string(28) "Rondleiding, educatieve reis"
  }
  [47]=>
  array(12) {
...
  }
}
</pre>
<pre class=nodisplay>test:

string(220) "ArrayArrayArrayArrayArrayArrayArrayArrayArrayArrayArrayArrayArrayArrayArrayArrayArrayArrayArrayArrayArrayArrayArrayArrayArrayArrayArrayArrayArrayArrayArrayArrayArrayArrayArrayArrayArrayArrayArrayArrayArrayArrayArrayArray"
</pre>

Was mache ich falsch?

fragt Linuchs

  1. problematische Seite

    Hallo Linuchs,

    Obwohl file_put_contents ein array zulässt, kann ich es von Datei mit file_get_contents nicht wieder herstellen:

    Du musst das Handbuch schon genau lesen: der zweite Parameter darf ein eindimensionales Array sein, die Elemente des Arrays werden dann aneinander gehängt und in die Datei geschrieben.

    Du suchst var_export() (true als 2. Parameter nicht vergessen), damit kannst du die Variable als PHP-Code in die Datei schreiben.

    Gruß
    Tobias

    1. problematische Seite

      Hallo tk,

      was es nicht alles gibt 😂 - den kannte ich noch gar nicht.

      Aber wenn, dann var_export($array, true), damit ein String rauskommt und das Ergebnis nicht direkt ausgegeben wird. Und wenn man es als PHP Code verwenden will, dann so:

      file_put_contents("cache/data.php", "<?php return " . var_export($array, true) . ";");
      
      $test = include "cache/data.php";
      

      Den cache-Ordner sollte man dann übrigens außerhalb des Webroot ablegen, den müssen User nicht abrufen können.

      Wie wär's übrigens mit serialize/unserialize? Der Output ist ein gutes Stück kleiner, und man muss nicht PHP Code von "irgendwo" ausführen. Ich würde dafür als Extension dat verwenden und nicht php, denn ausführbarer PHP Code ist es nicht.

      file_put_contents( "cache/arr_termintypen.dat", serialize($arr_termintypen));
      
      $test = unserialize(file_get_contents( "cache/arr_termintypen.dat" ));
      

      Es mag allerdings beim Einlesen langsamer sein, weil es kein PHP Code ist und deshalb nicht im Opcode-Cache landet. Ob man es messen kann? Dafür bräuchte man vermutlich Testdaten im Umfang von 1 MB...

      Rolf

      --
      sumpsi - posui - obstruxi
      1. problematische Seite

        Hallo Rolf,

        so, Testdaten erzeugt. Meine data.php ist 1,7MB groß, 4000 Wiederholungen der Row, die Linuchs oben als Beispiel gegeben hat.

        50 Includes dauern 1,2 Sekunden oder 25ms pro Vorgang.

        50 unserializes dauern 0,33 Sekunden oder 7ms pro Vorgang.

        Das war allerdings mein Kommandozeilen-PHP 8.02 unter Windows; es mag auf einem Webserver mit besserem Opcode-Cache besser für die include-Variante aussehen.

        Es hängt aber auch am PHP, mit PHP 5.6 waren es 29ms für die include-Version und 39ms für die unserialize-Version!

        Mit PHP 7.4 sind es 21ms für die include-Version und 5ms für die unserialize-Version.

        Sehr merkwürdig, dass PHP 8 beim unserialize langsamer geworden ist, ich muss wohl mal die PHP.INI Dateien der Versionen vergleichen...

        Rolf

        --
        sumpsi - posui - obstruxi
    2. problematische Seite

      Hallo Tobias,

      danke, funktioniert:

      file_put_contents( "cache/arr_termintypen.php", var_export( $arr_termintypen, TRUE ));
      unset( $arr_termintypen );
      $arr_termintypen            = file_get_contents( "cache/arr_termintypen.php" );
      

      Gruß Linuchs

      1. weiß nicht, warum das geklappt hat, jetzt nicht mehr:

        if ( is_array( $arr_termintypen )) echo "arr_termintypen IS array<br>"; else echo "arr_termintypen KEIN array<br>"; 
        unset( $arr_termintypen );
        $arr_termintypen            = file_get_contents( "cache/arr_termintypen.php" );
        if ( is_array( $arr_termintypen )) echo "arr_termintypen IS array<br>"; else echo "arr_termintypen KEIN array<br>"; 
        

        arr_termintypen IS array

        arr_termintypen KEIN array

        In der Datei steht

        array (
          24 => 
          array (
            'TYP' => '24',
            'anzahl_termine' => '0',
            'letzter_tag' => NULL,
            'typgruppe_id' => '1',
            'color' => '#f2004c',
            'sort' => 'bil_fue',
            'typ_beschreibung' => '',
            'gruppe_de' => 'Bildung',
            'name_de' => 'Führung, Bildungsreise',
            'gruppe_en' => 'Education',
            'name_en' => 'Guided tour, Educational journey',
            'gruppe_nl' => 'Onderwijs',
            'name_nl' => 'Rondleiding, educatieve reis',
          ),
          47 => 
          ...
        
        1. Hallo Linuchs,

          $arr_termintypen = file_get_contents( "cache/arr_termintypen.php" );

          file_get_contents ist die falsche Funktion um den Code zu lesen und als PHP-Array zur Verfügung zu haben. Du musst eine richtige PHP-Datei draus machen (also <?php und return vor das eigentliche Array) und include/require verwenden, siehe Antwort von Rolf.

          Gruß
          Tobias

          1. Hallo tk,

            ja, file_get_contents holt den Dateiinhalt als String, führt ihn aber nicht aus. Dafür müsste man einen eval("return " + $filecontent + ";") nachschalten (Ahhh! Nicht hauen!!!)

            Wenn man ausführbaren Code erzeugen will, dann "<?php return " vor den var_export und ein ";" dahinter. Das Ergebnis lässt sich dann includen.

            Ob das schneller oder langsamer ist als serialize/unserialize muss man dann auf dem konkreten System messen. Der Opcode-Cache dürfte hier eine wesentliche Rolle spielen.

            json_encode/json_decode statt serialize/unserialize geht auch, es produziert in diesem Fall hier sogar eine deutlich kleinere Datei. Aber in meinem Test braucht unserialize 6 ms für 4000 Zeilen und json_decode 15 ms (im Vergleich zu den 21.5ms für include). Und man muss aufpassen, json_decode mit true als 2. Parameter aufzurufen, damit man Arrays bekommt und nicht std_class Objekte.

            Rolf

            --
            sumpsi - posui - obstruxi
      2. problematische Seite

        Hallo Tobias und Rolf,

        so funktioniert es jetzt:

        erstelleArrayTermintypen.php:

        // zusätzlich zum internen Array
        file_put_contents( "cache/arr_termintypen.php", "<?php\n" . '$arr_termintypen = ' . var_export( $arr_termintypen, TRUE ) . "\n?>" );
        

        Hauptprogramm:

        include_once ( "erstelleArrayTermintypen.php" );  // alle TYPen in de, en, nl
        unset( $arr_termintypen );
        // erstellte cache-Datei als PHP-array einbinden
        include_once ( "cache/arr_termintypen.php" );     // array
        

        Linuchs

        1. problematische Seite

          Hallo Linuchs,

          nein, tu das nicht. Fixiere nicht den Namen der Variablen im include, das ist eine unnötige Abhängigkeit. Je weniger Koppelungen du hast, desto besser. Mach es deshalb so wie von mir gezeigt. Einen return ins Include und dann

          $arr_termintypen = include "cache/arr_termintypen.php";

          Keinen include_once - du includest ja keine Funktionen. Vielleicht MÖCHSTEST Du die Termintypen ja an 2 Stellen einlesen. Und auch keinen require. Denn hinter dem Einlesen sollte dies stehen:

          if ($arr_termintypen === FALSE) {
             $arr_termintypen = loadTerminTypen();   // aus der Datenbank
             file_put_contents("cache/arr_termintypen.php", ...)
          }
          

          Dann brauchst Du nur den Cache zu löschen und der nächste Abruf erzeugt die Datei neu. Die Warning, die der include auf eine fehlende Datei schreibt, landet sicherlich nur bei Dir im Log und nicht im Browser, gelle?

          Hast Du mal die Laufzeit von unserialize auf deinem Server mit der von include verglichen?

          Rolf

          --
          sumpsi - posui - obstruxi
  2. problematische Seite

    Hello,

    dir könnten die Funktionen rund um parse_ini_file() helfen.

    Ich habe dafür auch mal eine Umkehrfunktion geschrieben. Vielleicht ist die noch im Wiki vorhanden...

    siehe hier

    Glück Auf
    Tom vom Berg

    --
    Es gibt nichts Gutes, außer man tut es!
    Das Leben selbst ist der Sinn.
  3. problematische Seite

    Tach!

    Obwohl file_put_contents ein array zulässt, kann ich es von Datei mit file_get_contents nicht wieder herstellen:

    Dass man ein Array angeben kann, ist wohl file() geschuldet, denn das liefert ein Array, mit je einer Zeile pro Element. Und auch nur ein solches wird von file_put_contents() geschrieben. Alles andere wird zu String konvertiert. Wenn man ein Array zu String konvertiert kommt der String "Array" raus, und der eigentliche Inhalt geht verloren. Das passiert bei dir mit den Elementen ab der zweiten Ebene. Wenn du Datenstrukturen verlustfrei in eine Textdatei schreiben und wieder lesen möchtest, kannst du sie serialisieren. Damit sind sie ein einzelner langer String. Für das Lesen gibt es eine Umkehrfunktion.

    dedlfix.

  4. problematische Seite

    printf('Hallo %s!', ['Du', 'ihr', 'Welt', 'zusammen'][rand(0, 3)]);

    für die Termintypen von Veanstaltungsdaten erzeuge ich ein 2-dimensionales Array, das sich monatelang nicht ändert. Also möchte ich es nicht bei jedem Programmlauf per SQL und PHP neu erzeugen (0.04900s), sondern einmalig in eine Datei schreiben und die Datei als PHP-Code lesen (0.00000s).

    Obwohl file_put_contents ein array zulässt, kann ich es von Datei mit file_get_contents nicht wieder herstellen:

    Arraywerte speichert und liest man am einfachsten (MNSHO) als JSON. Kann man dann mit file_(put|get)_contents problemlos schreiben/lesen

    /K

    --
    Klingonen sind doof. Sie rufen ständig nach einem Kaplan und wollen nach dem Tod in Styropor® sein.