Linuchs: php-Code aus Datenbank holen und als lauffähigen Code ins Programm einbinden?

Moin,

alte, vermutlich nirgends mehr genutzte Programmcodes müllen mir Verzeichnisse zu. Da gibt es was von 2006, von dem ich nicht weiß, ob und wo es noch gebraucht wird.

Soweit mir bekannt, müssen per include eingebundene PHP-Schnipsel als Datei vorliegen. Einer Datei kann man aber nicht ansehen, von welchen Programmen sie aufgerufen werden.

Wenn ich nun die Programmcode in der Datenbank als text hinterlege, könnte ich mir einen Verwendungsnachweis basteln.

Zum Ändern des Codes muss ich ihn auslesen, im Editor bearbeiten und wieder abspeichern. Das schreckt mich nicht. Sollte nicht wesentlich umständlicher sein als eine Textdatei aus dem Editor abzuspeichern, zum Filezilla wechseln, ggf. links und rechts das directory neu suchen und die geänderte Datei hochladen.

Aber wie baue ich den aus der Datenbank gelesenen Text als lauffähigen Code ins Programm ein?

Gruß, Linuchs

  1. Hallo Linuchs,

    etwas verwirrend, wie du das genau meinst. Es geht sowohl extern als intern durch PHP-Deklaration, aber das kennst du ja, daher meinst du sicher was anders. Vielleicht etwas was in Richtung EVAL mit all seinen Gefahren geht?

    Gruss
    Henry

    --
    Meine Meinung zu DSGVO & Co:
    „Principiis obsta. Sero medicina parata, cum mala per longas convaluere moras.“
  2. Lieber Linuchs,

    der von Dir vorgeschlagene Weg löst nicht Dein ursprüngliches Problem. Er birgt aber neben unmöglicher Wartung noch viel mehr das Problem, dass Du durch unnötige Komplexität Dein System unnötig kaputt machst. Wo holt sich Deine Applikation denn die Zugangsdaten zur DB? Und womit? Richtig, mit PHP-Code. Aber der steht doch Deiner Idee nach in der DB...

    alte, vermutlich nirgends mehr genutzte Programmcodes müllen mir Verzeichnisse zu. Da gibt es was von 2006, von dem ich nicht weiß, ob und wo es noch gebraucht wird.

    Dann suche doch in den Dateien nach Hinweisen, von wo aus dieser Code benutzt wird. Das sollte im Notfall lokal auf Deinem Rechner mit einer passenden Funktion Deines Code-Editors mühelos gehen.

    Soweit mir bekannt, müssen per include eingebundene PHP-Schnipsel als Datei vorliegen. Einer Datei kann man aber nicht ansehen, von welchen Programmen sie aufgerufen werden.

    Es gilt unverändert: eval is evil. Das solltest Du unbedingt lassen!

    Wenn ich nun die Programmcode in der Datenbank als text hinterlege, könnte ich mir einen Verwendungsnachweis basteln.

    Was ein Quatsch! Du hast einen(!) Benutzer, dessen DB-Zugangsdaten Du loggen kannst. Du kannst aber nicht loggen, welche Programmcodes auf Deine DB zugegriffen haben. Und was zuerst in Dateiform vorgelegen hat, kann nicht "einfach" später aus der DB geladen und ausgeführt werden.

    Sollte nicht wesentlich umständlicher sein als eine Textdatei aus dem Editor abzuspeichern, zum Filezilla wechseln, ggf. links und rechts das directory neu suchen und die geänderte Datei hochladen.

    Aber doch! Um den in der DB gespeicherten "Code" (in einer DB speichert man Daten!) überhaupt zu erreichen, benötigst Du... Code. Und da beißt sich ab einem bestimmten Moment die Katze selbst in den Schwanz. Nein, mit mehr Komplexität wird kein System wirklich besser wartbar.

    Aber wie baue ich den aus der Datenbank gelesenen Text als lauffähigen Code ins Programm ein?

    Besser überhaupt nicht. Finde einen anderen Weg (mit Menschen sprechen), um Dein eigentliches Problem (was soll der Code/von wem ist der Code) zu lösen.

    Liebe Grüße

    Felix Riesterer

    1. Tach!

      Es gilt unverändert: eval is evil. Das solltest Du unbedingt lassen!

      Das ist ein sehr plakativer Spruch, aber man darf da durchaus differenzieren. Es gibt jedefalls keinen grundsätzlichen Unterschied zwischen eval() und einer Einbindung per include/require. Es muss in beiden Fällen sichergestellt sein, dass der Code nicht manipuliert worden ist.

      dedlfix.

      1. Hello,

        Tach!

        Es gilt unverändert: eval is evil. Das solltest Du unbedingt lassen!

        Das ist ein sehr plakativer Spruch, aber man darf da durchaus differenzieren. Es gibt jedefalls keinen grundsätzlichen Unterschied zwischen eval() und einer Einbindung per include/require. Es muss in beiden Fällen sichergestellt sein, dass der Code nicht manipuliert worden ist.

        Genau das wollte ich gestern auch schreiben, aber Du warst schneller.
        Die Warnung im PHP-Manual halte ich daher auch für irreführend. Sie wird dort leider nicht weiter relativiert oder erklärt.

        Jedenfalls ist ein kaputtes File-Upload-Design verbunden mit Include-Dateien, die innerhalb der Document Root liegen genauso gefährlich.

        Glück Auf
        Tom vom Berg

        --
        Es gibt nichts Gutes, außer man tut es!
        Das Leben selbst ist der Sinn.
        1. Lieber Tom,

          Es gilt unverändert: eval is evil. Das solltest Du unbedingt lassen!

          Das ist ein sehr plakativer Spruch, aber man darf da durchaus differenzieren. Es gibt jedefalls keinen grundsätzlichen Unterschied zwischen eval() und einer Einbindung per include/require. Es muss in beiden Fällen sichergestellt sein, dass der Code nicht manipuliert worden ist.

          Genau das wollte ich gestern auch schreiben, aber Du warst schneller.
          Die Warnung im PHP-Manual halte ich daher auch für irreführend. Sie wird dort leider nicht weiter relativiert oder erklärt.

          Jedenfalls ist ein kaputtes File-Upload-Design verbunden mit Include-Dateien, die innerhalb der Document Root liegen genauso gefährlich.

          Das gilt wohl besonders für die berühmte index.php usw. und die .htaccess, wenn die nicht gegen Überschreiben geschützt werden und der Uploaddialog freie Namensvergabe (innerhalb der Docoument Root) erlaubt.

          Namen und Pfade, deren Ziel außerhalb der Document Root liegen, sind ohnehin inakzeptabel.

          Spirituelle Grüße
          Dein Robert

          --
          Möge der Forumsgeist ewig leben!
  3. Hallo Linuchs,

    in einer von mir mit betreuten Anwendung hat der Autor etwas ähnliches gemacht. Da gibt es Wettkämpfe, und jeder Wettkampftyp wird durch eine bestimmte PHP Datei verwaltet. Er hat also in der DB eine Tabelle, wo steht: Wettkampftyp 1 ist "Team-Ibsen" [1] und wird von ibsen_1.php verwaltet. D.h. macht man eine neue Version von ibsen_1.php, kann man ibsen_2.php hochladen und Test-Wettkämpfe mit Typ 2 anlegen. Nach Freigabe von ibsen_2 wird dann der Default-Wettkampftyp für "Team-Ibsen" auf 2 geändert und der neue Code ist live. Schien zu Beginn sehr praktisch und sinnvoll, endet aber am Ende doch in einer Versions Hölle sondergleichen. Man hat alte Wettkämpfe im Archiv und muss den Code dafür online halten. Not Good, wie Happy Quinn zu sagen pflegt.

    Deine Frage, welcher Code von was verwendet wird, sollte sich relativ leicht durch ein grep beantworten lassen, das alle include und require heraussucht - es sei denn, du hast sowas wie include($xyModul) in deinen Programmen stehen. Oder Du verwendest weitere Tools, die Code nachladen (z.B. Composer oder ein Class-Autoloader). Dagegen hilft dann nur Ordnung und Dokumentation, und im Zweifelsfall geduldiges Nachdokumentieren.

    Aber den grep include|require brauchst Du eh, wenn Du deinen "Code-From-DB" Loader einbauen willst, oder? Deswegen sehe ich ebenfalls noch nicht den Nutzen. Aber wenn Du's tun willst - mach eine Funktion, die den Code aus der DB nach php://temp schreibt und dann von dort includet. Gemäß dieser Beschreibung sollte das möglich sein.

    Rolf

    --
    sumpsi - posui - clusi

    1. Kennen Sie Ibsen? Nein, wie geht denn das? ↩︎

  4. Tach!

    alte, vermutlich nirgends mehr genutzte Programmcodes müllen mir Verzeichnisse zu. Da gibt es was von 2006, von dem ich nicht weiß, ob und wo es noch gebraucht wird.

    Eine IDE mit statischer Code-Analyse kann helfen, die Stellen der Verwendung zu finden. Voraussetzung ist, dass die Verwendung auch statisch ist und nicht über Magic Strings oder variable Variablen erfolgt.

    function foo() { /* ... */ }
    
    //...
    
    foo();
    

    Das obige wäre eine statisch nachvollziehbare Verwendung, das nachfolgende nicht.

    $bar = 'foo';
    $bar();
    

    JetBrains PhpStorm ist eine solche IDE, die Verwendungen finden kann. Auch auf Dateien kann man das "Find Usages" anwenden und bekommt die entsprechenden include-Anweisungen aufgelistet. Voraussetzung ist hier auch, dass der Dateiname nicht aus String-Einzelteilen zusammengebaut wird.

    Soweit mir bekannt, müssen per include eingebundene PHP-Schnipsel als Datei vorliegen. Einer Datei kann man aber nicht ansehen, von welchen Programmen sie aufgerufen werden.

    Die Datei erzählt sowas nicht, aber selbst wenn eine IDE nicht helfen kann, ein Stück Code kann helfen. debug_backtrace() gibt dir einen Weg an, der zum Aufruf geführt hat. Damit kannst du zur Laufzeit die verweisenden Stellen finden. Am besten schreibt man das wohl in ein separates Logfile. Allerdings kann das auch nur dann Aufrufe finden, wenn man sich zufällig oder gezielt zu den Programmteilen begibt, die den Aufruf auslösen.

    Wenn ich nun die Programmcode in der Datenbank als text hinterlege, könnte ich mir einen Verwendungsnachweis basteln.

    Das wäre ein ähnliches Vorgehen wie beim debug_backtrace(), nur viel komplexer.

    Aber wie baue ich den aus der Datenbank gelesenen Text als lauffähigen Code ins Programm ein?

    Es bleibt sich gleich, ob du den Code als (temporäre) Datei schreibst und dann inkludierst oder direkt per eval() auswertest.

    dedlfix.

    1. Tach!

      Wenn ich nun die Programmcode in der Datenbank als text hinterlege, könnte ich mir einen Verwendungsnachweis basteln.

      Das wäre ein ähnliches Vorgehen wie beim debug_backtrace(), nur viel komplexer.

      Nachtrag: Das Problem löst sich nicht, indem der Code anderswohin geschoben wird. Man weiß dadurch immer noch nicht, woher der Aufruf kommt. Schlimmstenfalls muss man das sogar vorab wissen, damit die Aufrufstellen geändert werden können, so dass sie den neuen Aufruf ausführen können. Ein include/require ist nicht per se abfangbar. Lediglich ein Autoload-Mechanismus existiert in PHP, aber nur für Klassen. Und man muss dafür alle include-Aufrufe entfernen. Wenn man das jedoch tut, findet man dabei alle Aufrufe, weil man diese includes löschen muss. Damit hätte sich das Thema dann auch von selbst gelöst.

      Wenn also mit Funktionen einer IDE oder anderweitigen Tools die Aufrufstellen nicht gefunden werden können, hilft nur ein Stacktrace in den aufgerufenen Dateien.

      dedlfix.

  5. Hello Karl-Heinz,

    prinzipiell fand ich diese Vorgehensweise (Code in der Datenbank) auch mal ganz sympathisch. Schließlich kann man dann jegliche Rechte auf irgendwelche Module bequem steuern und ausgefeilte Statistiken betreiben. Die quasi objektorientierte Behandlung, in der jedes HTML-Teilmodul seinen eigenen Code mitbringt, war schon reizvoll.

    Schwierigkeiten macht dann aber die Betreuung und Übersicht des Codes.
    Auch muss man etwas gegen Mehrfacheinbindungen und Reentranz tun.

    Wir haben deshalb mal ein ganzes (kleines) CMS darauf aufgebaut. Schlussendlich haben wir dann aber mehr Gehirnschmalz und Arbeit auf did Pflegefunktionen (eine "IDE") verschwendet, als auf das eigentliche System.

    Irgendwann nach ca. acht Jahren ist das Projekt dann auch gestorben.

    Und nicht zu vernachlässigen ist auch die erhebliche Steigerung der Datenbanklast.

    Ich würde Dir daher heute eher zu einem guten Versionsverwaltungssystem und zu akribischer Dokumentation sowie sauberer Trennung von shared Objects (für mehrere Projekte nutzbar, "Basis-Werkzeugkasten") und projektbezogenen Klassen, Methoden, Modulen, Funktionen, ... raten.

    Glück Auf
    Tom vom Berg

    --
    Es gibt nichts Gutes, außer man tut es!
    Das Leben selbst ist der Sinn.
  6. Wenn ich nun die Programmcode in der Datenbank als text hinterlege, könnte ich mir einen Verwendungsnachweis basteln.

    Dafür gibts Kommentare, zweckmäßige Dateinamen und eine im Dateisystem der Klassenhierarchie entsprechende Verzeichnisstruktur. In Perl ists z.B. üblich eine Manifest-Datei zu erstellen und die komplette Dokumentation als POD im Code einzubetten woraus man je nach Bedarf Text oder HTML -Dateien erstellen kann. MFG

  7. Nachdem ich mich gestern an anderer Stelle aufgeregt habe, dass Dir keine einfache und wirklich passende Lösung angeboten wurde, hole ich das zum Abschied und zum Nachweis, dass ich kein Klug- und erst recht kein Dummschwätzer bin, nach:

    Deine ursprüngliche Idee habe ich einfach mal herumgedreht. Die aufrufenden Skripte bleiben unverändert.

    Schritt 1:

    Dafür sorge tragen, dass der Webserver in das betreffende Verzeichnis mit den zu incudierenden lib-Dateien schreiben kann.

    Schritt 2:

    Diesen etwas langen "Einzeiler" in die "verdächtigen" libs setzen:

    <?php
    if ( is_file( __DIR__  . '/flag_log_opener_true' ) && ! is_file(__FILE__ . '_opener.txt' ) ) file_put_contents( __FILE__ . '_opener.txt' , realpath( $_SERVER['SCRIPT_NAME'] ) . PHP_EOL , FALSE );
    

    (Ich denke, es ist selbsterklärend, was das macht. Wenn nicht: fragen, das sollten hier so zweidrei mehr beantworten können)

    Schritt 3:

    In der Shell mit touch flag_log_opener_true eine leere Datei im lib-Dir anlegen.

    Schritt 4:

    Warten oder wie unter c) beschrieben vorgehen.

    Schritt 5:

    Dateien mit der Endung "_opener.txt" bestaunen. Deren Existenz und Inhalt zeigt, dass die Datei, an deren Name "_opener.txt" angehangen wurde, mindestens einmal aufgerufen bzw. includiert wurde. ls reicht also um im von Dir bestimmten Umfang informiert zu sein.

    Schritt 6:

    mv flag_log_opener_true flag_log_opener_false; rm *_opener.txt

    Diskussion:

    a) Vorteile:

    1. Die Performancebremse "Datenbank" und vermeingefährliches Zeug wie eval() werden nicht genutzt.
    2. Bei meinem Test mit halbwegs leistungsfähiger Harware: 1 Mio Aufrufe der Zeile = 1.25 sek. - Ich denke aber, ein Einzelaufruf ist deutlich "teurer".
    3. Das Vorgehen ist vergleichsweise "simple, stupid and also inexpensive".
    4. Funktioniert auch bei dynamischer Einbindung.
    5. Stoppen einfach durch Löschen oder Umbenennen der Datei. 'flag_log_opener'. Und also leicht reaktivierbar.
    6. Keine "Überinformation" wie bei debug_backtrace().
    7. Keine Änderung Deiner Arbeitsweise nötig.

    b) Nachteile:

    1. Setzt einen Abruf voraus, es gibt also keine Garantie, dass ein Aufruf registriert wird obwohl das Skript doch noch irgendwo eingebunden ist.
    2. Braucht die Schreibrechte am Verzeichnis.
    3. Du änderst Deine Arbeitsweise nicht. Eine IDE wäre tatsächlich hilfreicher. (Übrigens scheitern die auch bei vielen dynamischen Einbindungen)

    c) Behebung der Nachteile und deren Folgen:

    • Der Abruf kann durch etwas wie wget --delete-after -mrk https://example.com/ erzwungen werden. Das füllt aber keine Formulare aus, ebenso wird Javascript nicht ausgeführt. Außerdem hält sich wget an die robots.txt (man wget zeigt wie man das ändert)
    • Anderes Verzeichnis benutzen.

    Es gibt eine weitere Variante:

    <?php
    if ( is_file( __DIR__  . '/flag_log_opener_true' ) ) file_put_contents( __FILE__ . '_opener.txt' , realpath( $_SERVER['SCRIPT_NAME'] ) . PHP_EOL, FILE_APPEND );
    

    Diese schreibt jeden Zugriff mit und macht also mehr als gefordert. Das kann aber ohne "teure" Gegenmaßnahmen zum Überlauf der Platte führen. Wenn Du nun

    <?php
    if ( is_file( __DIR__  . '/flag_log_opener_true' ) ) file_put_contents( __FILE__ . '_opener.txt' , date('Y-m-d H:i:s') . PHP_EOL . var_export( debug_backtrace(), TRUE ) . PHP_EOL . PHP_EOL, FILE_APPEND );
    

    einträgst, dann bist Du praktisch bei einer Variante der Lösung von dedlfix. Dieses debug_backtrace() liefert halt mehr Informationen als Du wolltest. Kann aber sein, genau die interessieren jemand anderes.

    1. EDIT: Folgendes wurde im eigentlichen Posting geändert.

      Die Zeile

      mv flag_log_opener_true flag_log_opener_false; rm * _opener.txt
      

      muss durch

      mv flag_log_opener_true flag_log_opener_false; rm *_opener.txt
      

      ersetzt werden. Das überflüssige Leerzeichen zwischen * und _opener.txt kann wird bei Unaufmerksamkeit und/oder falscher Kompilierung des Befehlesrm dazu führen, dass sämtliche Dateien in dem Vereichnis gelöscht werden.

    2. Hello,

      vielleicht könnte man auch inotify dafür benutzen, sich Übersicht über Abhängigkeiten zu verschaffen. Habe ich aber noch nicht probiert.

      Aber vermutlich wird als Mutterprozess immer nur der zuständige Apache-Prozess aka Webserver aufgelöst. Dann bleibt einem als Aussage nur noch die Reihenfolge der geloggten Fileaktivitäten.

      Glück Auf
      Tom vom Berg

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

      Schritt 2:

      Diesen etwas langen "Einzeiler" in die "verdächtigen" libs setzen:

      In alle Dateien einfügen, die das Ziel aufrufen könnten, und ja keine vergessen. Das ist ziemlich aufwendig. Am Ende muss das auch wieder aufgeräumt werden. Meine debug_backtrace()-Variante kommt damit aus, sie in einer oder wenigen Dateien eingefügt wird. Vielleicht reicht auch schon, wenn lediglich $_SERVER['REQUEST_URI'] oder $_SERVER['SCRIPT_FILENAME'] mitgeloggt wird. Damit sieht man zumindest die angefragte URL/Datei, nicht jedoch die Aufrufkette, die zur Einbindung der fraglichen Dateien führt. Kann auch schon helfen, denn damit fallen auch weniger auszuwertende Daten an. Man bekommt so zumindest einen Anhaltspunkt, um dann im Labor genauer zu schauen.

      dedlfix.

      1. Hallo dedlfix,

        nein, das kommt ja in die aufgerufenen Dateien hinein. Dadurch wird protokolliert, durch welchen Seitenabruf der include ausgelöst wird.

        Für denn Fall, dass der Include zweistufig erfolgt, hat man natürlich etwas Folgearbeit.

        Und den hier:

        es gibt also keine Garantie, dass ein Aufruf registriert wird

        kann man nicht lösen, sobald die Includes über Variablen stattfinden. Da hilft dann nur Code-Analyse, um herauszufinden, welche möglichen Werte zustandekommen können und durch welche Aktivitäten.

        Includes, die nicht über Variablen stattfinden, findet man aber einfacher und sicherer durch grep auf den Sourcecode. Und nicht über Laufzeitprotokolle.

        Insofern: Ich frage mich, ob Jörgs Vorschlag tatsächlich zielführend ist. Aber das kann nur Linuchs beurteilen, er kennt seinen Code und weiß, welche Arten von Includes er hat. Der grep ist aus meiner Sicht definitiv der erste Schritt, und nur die Includes, die Variablen verwenden und die Files, die über grep keinen Kunden nachweisen können, muss man genauer betrachten.

        Rolf

        --
        sumpsi - posui - clusi
        1. Tach!

          nein, das kommt ja in die aufgerufenen Dateien hinein. Dadurch wird protokolliert, durch welchen Seitenabruf der include ausgelöst wird.

          Da hab ich wohl diese unformatierte Codewurst sowie "verdächtige Libs" nicht richtig gedeutet. Verdächtigen würde man ja die Aufrufer und nicht die Aufgerufenen, denn die sind ja bekannt.

          dedlfix.

    4. Lieber Fastix,

      ich habe deine Postings immer gerne gelesen.

      Sag zum Abschied leise Servus!

      Oder lass den Abschiedsblödsinn lieber. Du kommst ja (hoffentlich) sowieso wieder!

      Spirituelle Grüße
      Dein Robert

      --
      Möge der Forumsgeist ewig leben!
  8. Aloha ;)

    Soweit mir bekannt, müssen per include eingebundene PHP-Schnipsel als Datei vorliegen. Einer Datei kann man aber nicht ansehen, von welchen Programmen sie aufgerufen werden.

    Richtig. Du weißt also nicht, welche Programme diese PHP-Schnipsel einladen.

    Wenn ich nun die Programmcode in der Datenbank als text hinterlege, könnte ich mir einen Verwendungsnachweis basteln.

    Die Idee ist theoretisch schon okay, aber du übersiehst das Kernproblem. Es steckt hier:

    Aber wie baue ich den aus der Datenbank gelesenen Text als lauffähigen Code ins Programm ein?

    Die Kernfrage ist nicht wie, sondern wo. Um ihn als lauffähigen Code in den Dateien einzubinden, die die PHP-Schnipsel benutzen, musst du wissen, welche das sind. Das ist aber genau das, was du ursprünglich mal herausfinden wolltest.

    Du drehst dich mit dem Problem also im Kreis.

    Je nachdem wie sicher du dir bist, dass die nicht mehr verwendet werden, kannst du die Skripte einfach umbenennen. Allerdings nach Möglichkeit vielleicht nicht im Produktivsystem, sondern in einer ausgiebig testbaren Testinstallation. Wenn alle Features auch nach dem Verschieben/Umbenennen funktionieren, werden sie wohl nicht mehr gebraucht.

    Sowas ist übrigens, als Randnotiz, auch ein starkes Argument für unit-tests und Co., das aber nur am Rande.

    Grüße,

    RIDER

    --
    Camping_RIDER a.k.a. Riders Flame a.k.a. Janosch Zoller
    # Twitter # Steam # YouTube # Self-Wiki # Selfcode: sh:) fo:) ch:| rl:) br:^ n4:? ie:% mo:| va:) js:) de:> zu:} fl:( ss:) ls:[