Melvin Cowznofski: Doppelter Klassenaufruf

Hallo,

ich stehe vor einem Fehler, den ich zwar beheben konnte, ihn aber trotzdem nicht nachvollziehen kann. Ich hoffe, ich kann das jetzt so verständlich erklären, dass man versteht, was ich meine:

Gegeben ist eine Klassen-Ressource Foo mit einer simplen Methode. In einer index.php wird eine weitere Klasse definiert, in der die Klasse Foo eingefügt, deren Methode aufgerufen und das Ergebnis zurückgegeben wird:

Foo.php:

<?php
  Class Foo
    {
      public function hallo()
        {
          return 'Hallo Welt!';
        }
    }
?>

Index.php:

<?php
  Class Bar
    {
      public function barFunction()
        {
          require('Foo.php');
          $foo = new Foo;
          $test = $foo -> hallo();
          return $test;
        }
    }
$bar = new Bar;
$test_1 = $bar -> barFunction();
var_dump($test_1);
?>

Zurückgegeben wird wie erwartet ein "Hallo Welt!". Führe ich in der index.php jetzt aber die Methode barFunction() ein 2. Mal aus, kommt es zu einer Fehlermeldung.

$test_2 = $bar -> barFunction();
var_dump($test_2);

// Das führt zu "Fatal error: Cannot declare class Foo, because the name is already in use in … on line 3"

Ich habe darauf intuitiv reagiert und die Instanz der Klasse Foo als private Eigenschaft der Klasse Bar gespeichert. Nun funktioniert die doppelte Verwendung von $bar wie von Anfang an gewünscht:

Veränderte index.php:

<?php
Class Bar
  {
    private $_foo;

    public function __construct()
      {
        require('Foo.php');
        $this -> _foo = new Foo;
      }

    public function barFunction()
      {
        $test = $this -> _foo -> hallo();
        return $test;
      }
  }

  $bar = new Bar;
  $test_1 = $bar -> barFunction();
  var_dump($test_1);
  $test_2 = $bar -> barFunction();
  var_dump($test_2);
?>

Wie gesagt, das behebt das Problem. Aber eben nur intuitiv, nicht verständnismäßig. Ich schau mir die Sache jetzt seit 1 Stunde an und denke durch, was der Reihe nach passiert. Aber ich sehe beim 1. Versuch einfach nicht, wo hier die Klasse Foo ein 2. Mal aufgerufen wird. Ich verwende doch die selbe Instanz der Klasse bar, in der Foo ja nur einmalig aufgerufen wird. Wieso wird also der 2. Aufruf bemängelt und wo passiert der? Stehe ich wirklich so auf der Leitung?

Mit lieben Grüßen

Melvin Cowznofski

--
What – me worry?
  1. Dein Problem:

    require('Foo.php');
    

    Die überraschend einfache Lösung:

    Skripte mit der Definition von

    • Klassen
    • Funktionen
    • Konstanten
    • e.t.c.

    einfach nur einmal includieren:

    require_once( 'Foo.php' );
    
    1. Hallo Raketenwissenschaftler,

      einfach nur einmal includieren:

      `require_once( 'Foo.php' );

      Danke für den Hinweis! Ja, das ist einfacher. Allerdings erklärt mir das noch immer nicht, wo/wieso es hier zu einem mehrfachen Aufruf kommt. Meine (offenbar falsche) Sichtweise ist, dass eine Instanz erzeigt wird, in der es ein mal zu einem Einfügen der fremden Klasse kommt. Und diese eine Instanz wird 2 mal verwendet. Wo ist mein Denkfehler?

      Mit lieben Grüßen

      Melvin Cowznofski

      --
      What – me worry?
      1. Allerdings erklärt mir das noch immer nicht, wo/wieso es hier zu einem mehrfachen Aufruf kommt.

        Du hast alle Informationen:

        Führe ich in der index.php jetzt aber die Methode barFunction() ein 2. Mal aus, kommt es zu einer Fehlermeldung.

        Das require und damit der Versuch, die Klasse ein zweites mal zu definieren, findet doch genau in der Methode barFunction() statt.

        Hint: Wenn man beim Programmieren etwas nicht versteht, dann muss man sich meist noch dümmer stellen als man es schon tut und Intelligenz durch Sturheit und schöde Pedanterie ersetzen.

      2. dedlfix hat es im Vorbeigehen, wenngleich etwas verklausuliert und vor allem versteckt erklärt. Er schreibt:

        Private Klassen gibt es nicht. PHP legt sie auch in dem Fall global an.

        Anders als (nichtglobale) Variablen sind (in PHP) Klassen, Funktionen und Konstanten „superglobal“. Auch wenn Du diese innerhalb einer Funktion/Methode anlegst sind diese also „überall“ vorhanden (werden nach Abarbeiten der Funktion auch nicht „vergessen“) und dürfen ergo nicht nochmals angelegt werden.

        Für konkrete, aus einer Klasse abgleitete und in Variablen abgelegte Objekte (samt deren Properties und Methoden) gilt das übrigens nicht.

        Hint: Man kann mit function_exists( $name ), class_exists( $name ) und is_defined( $name ) prüfen, ob es das Zeug schon gibt.

        1. Hallo Raketenwissenschaftler,

          sorry, ich hatte ganz vergessen, mich zu bedanken und Feedback zu geben! Deine Antwort hat mir geholfen, die Sachlage zu verstehen, merci!

          Mit lieben Grüßen

          Melvin Cowznofski

          --
          What – me worry?
  2. Tach!

    Gegeben ist eine Klassen-Ressource

    Was ist eine Klassen-Ressource? Meinst du eine Datei mit der Klassendefinition darin?

    Führe ich in der index.php jetzt aber die Methode barFunction() ein 2. Mal aus, kommt es zu einer Fehlermeldung. [...] // Das führt zu "Fatal error: Cannot declare class Foo, because the name is already in use in … on line 3"

    Diese Meldung kommt auch, wenn du sowas schreibst.

    require('Foo.php');
    require('Foo.php');
    

    Oder wenn du den Code aus der Datei zweimal hintereinander schreibst.

    Ich habe darauf intuitiv reagiert und die Instanz der Klasse Foo als private Eigenschaft der Klasse Bar gespeichert.

    Das ist aber nicht dasselbe wie dein erster Code. Im ersten Fall wird bei jedem Aufruf eine neue Instanz der Klasse erstellt, in der Ersatzlösung wird dieselbe Instanz wiederverwendet. Das kann sogar eine Verbesserung sein, dass nur eine Instanz erstellt wird, aber je nach Fall auch nachteilig.

    Was versuchst du eigentlich zu erreichen, dass du die Klassendatei nicht generell, sondern innerhalb des Funktionsaufrufs inkludierst? Private Klassen gibt es nicht. PHP legt sie auch in dem Fall global an. Das heißt, du kannst das require() oder besser require_once() auch außerhalb von class Bar notieren, zum Beispiel am Anfang der Datei.

    dedlfix.

    1. Was versuchst du eigentlich zu erreichen, dass du die Klassendatei nicht generell, sondern innerhalb des Funktionsaufrufs inkludierst?

      Sowas kann durchaus sinnvoll sein:

      Wenn eine umfangreiche Klasse auf Grund der Datenlage nicht gebraucht wird - warum dann das Skript laden, es kompilieren und sodann die Klasse bereit halten?

      Ob das hier der Fall ist kann ich nicht wissen.

      1. Tach!

        Was versuchst du eigentlich zu erreichen, dass du die Klassendatei nicht generell, sondern innerhalb des Funktionsaufrufs inkludierst?

        Sowas kann durchaus sinnvoll sein:

        Wenn eine umfangreiche Klasse auf Grund der Datenlage nicht gebraucht wird - warum dann das Skript laden, es kompilieren und sodann die Klasse bereit halten?

        Mit dem in PHP 7 enthaltenen OpCode-Cache ist das kein Thema mehr. Die Kompilierverzögerung ist damit nur beim ersten Aufruf des Script vorhanden, und dann nicht mehr, egal wie oft die Datei anderenorts referenziert wird. Der Speicher im OpCode-Cache bleibt auch erstmal belegt und wird nicht am Scriptende wieder freigegeben.

        dedlfix.

        1. Der Speicher im OpCode-Cache bleibt auch erstmal belegt und wird nicht am Scriptende wieder freigegeben.

          Ja. Soweit er nicht anderweitig benötigt wird (z.B. auf einem shared Server mit 1000 Kunden und 900 Wordpress- oder gar Typo 3 Installationen) …

          1. Tach!

            Der Speicher im OpCode-Cache bleibt auch erstmal belegt und wird nicht am Scriptende wieder freigegeben.

            Ja. Soweit er nicht anderweitig benötigt wird (z.B. auf einem shared Server mit 1000 Kunden und 900 Wordpress- oder gar Typo 3 Installationen) …

            Für den Fall wäre es besser, keine Monsterdatei (oder eine mit vielen Abhängigkeiten) einzuladen, die erstmal jede Menge andere Dateien aus dem OpCode-Cache wirft. Damit entschärft sich das Problem auch.

            dedlfix.

            1. Hallo dedlfix,

              PHP 7? Ist das noch ein anderer Cache als der hier?

              Aber generell würde ich sagen: Wenn der Cache zu sehr in's Flattern kommt, sollte man ein paar RAM-Riegel auspacken. Oder einen anderen Server.

              Rolf

              --
              sumpsi - posui - obstruxi
              1. Tach!

                PHP 7? Ist das noch ein anderer Cache als der hier?

                Na gut, dann kam der OpCode-Cache schon mit 5.5. Aber mit PHP 7 kamen zumindest noch weitere Verbesserungen hinzu, aufgrund der umgeschriebenen Zend Engine.

                Aber generell würde ich sagen: Wenn der Cache zu sehr in's Flattern kommt, sollte man ein paar RAM-Riegel auspacken. Oder einen anderen Server.

                Ja. Und wenn man eine zu große Datei zu inkludieren gedenkt, dann hat man eher ein Problem mit der Code-Strukturierung. Herunterbrechen in überschaubar große Einheiten, sollte in allen Belangen zielführender sein, als sich Gedanken zu machen, wie man den im Hintergrund werkelnden Cache beeinflusst.

                dedlfix.

              2. (dedlfix:)

                Für den Fall wäre es besser, keine Monsterdatei (oder eine mit vielen Abhängigkeiten) einzuladen, die erstmal jede Menge andere Dateien aus dem OpCode-Cache wirft.

                Hm. Hatte ich denn mit…

                Sowas kann durchaus sinnvoll sein:

                Wenn eine umfangreiche Klasse auf Grund der Datenlage nicht gebraucht wird - warum dann das Skript laden, es kompilieren und sodann die Klasse bereit halten?

                … etwas grundsätzlich anderes behauptet? Das ist doch im Kern die gleiche Aussage.

                (Rolf:)

                PHP 7? Ist das noch ein anderer Cache als der hier?

                Naja. Es gab schon ein paar Bugfixes. Insofern könnte man das "ein anderer Cache" auch bejahen. Aber ich glaube, dedlfix hat sich nicht getraut, PHP 5.5 zu erwähnen, weil mir erst neulich im Hinblick auf eine ähnliche Erwähnung die Ablauffristen um die Ohren gehauen wurden…

    2. Hallo dedlfix,

      die Alternative ist

      spl_autoload_register(function ($class_name) {
          include $class_name . '.php';
      });
      

      Ja ok, eine produktionsreife Version davon ist etwas umfangreicher, und es gibt auch fertige Autoloader. Jedenfalls lädt man damit nur die tatsächlich verwendeten Klassen und muss sich über Includes von Class-Dateien keine Gedanken mehr machen.

      Rolf

      --
      sumpsi - posui - obstruxi
    3. Hallo dedlfix,

      Meinst du eine Datei mit der Klassendefinition darin?

      Ich habe doch in meiner ursprünglichen Frage den Code beider Ressourcen hingeschrieben. Ja, das meinte ich damit.

      Was versuchst du eigentlich zu erreichen, dass du die Klassendatei nicht generell, sondern innerhalb des Funktionsaufrufs inkludierst?

      Das ist ein Sonderfall, ich mache das sonst nicht so. In diesem Fall handelt es sich um eine Klasse einer PHP Bibliothek, die unter anderem für das Versenden von Mails via PHPMailer zuständig ist. Ich binde in dieser Klasse die PHPMailer Ressourcen, die in einem eigenen Verzeichnis liegen, ein. Und in diesen wiederum werden Klassen definiert. Und sobald jetzt innerhalb eines Skripts mehr als 1 Mail versendet wird, also 2 mal auf die entsprechende Klasse zugegriffen wird, werden die Klassendifinitionen von PHPMailer doppelt aufgerufen. Durch Deine Antwort bzw. der weiteren Erklärung vom Raketenwissenschaftler verstehe ich jetzt die Ursache der von mir geschilderten Problematik. Vielen Dank!

      Mit lieben Grüßen

      Melvin Cowznofski

      --
      What – me worry?