Kategorie: Tipps und Tricks zu PHP

Eine der Änderungen die sich von PHP 4 nach PHP 5 ergab war, dass die Konstruktoren von Klassen anders zu benennen sind. Der Konstruktor, also die Methode die automatisch ausgeführt wird, wenn eine Klasse instanziiert wird, musste in PHP 4 denselben Namen haben wir die Klasse selbst. In PHP 5 ist der Konstruktor aber anders zu bennen. Die Methode muss den Namen __construct() haben. Eine Klasse foo müsste in PHP 4 also so aussehen:

class foo
{
   function foo ()
   {
      echo "Ich bin der Konstruktor";
   }
}

die PHP 5 Variante sieht so aus:

class foo
{
   public function __construct ()
   {
      echo "Ich bin der Konstruktor";
   }
}

In beiden Varianten würde der Text Ich bin der Konstruktor ausgegeben, wenn ein neues Objekt der Klasse instanziiert wird. Die PHP 4-Variante kann übrigens auch mit PHP 5 ausgeführt werden. Um eine bessere Abwärtskompatibilität zu gewährleisten kann PHP 5 also mit Konstruktoren in beiden Varianten umgehen.

Jetzt stellt sich aber die interessante Frage wie eine Klasse sich verhält, wenn Konstruktoren in beiden Varianten da sind. Würde die Klasse mit PHP 4 ausgeführt, dann ist das (vielleicht) kein Problem, da PHP 4 den Konstruktor __construct() nicht kennt. Allerdings dürfte die Methode __construct() dann nicht als public function __construct() deklariert werden, sondern nur als function __construct(), da PHP 4 dieses Schlüsselwort och nicht kennt. Aber was passiert mit PHP 5? Wird ein Objekt dieser Klasse instanziiert, dann wird nur die PHP 5 Variante ausgeführt:

class foo
{
   public function foo ()
   {
      echo "Ich bin der PHP 4 Konstruktor";
   }
   public function __construct ()
   {
      echo "Ich bin der PHP 5 Konstruktor";
   }
}

Das heißt, es wird nur der Text Ich bin der PHP 5 Konstruktor ausgegeben. War eigentlich zu erwarten, oder? Interessant wird es aber, wenn die beiden Varianten gemischt werden, beipsielsweise enn die eine Klasse ein Elter ist und die andere Klasse abgeleitet ist. Diese Variante führt zu einem Fatal Error:

class foo
{
   public function __construct ()
   {
      echo "Ich bin der PHP 5 Konstruktor";
   }
}
 
class bar extends foo 
{
    public function bar()
    {
        parent::foo();
    }
}
 
new bar();

Logisch, die Methode foo() ist in der Superklasse ja auch nicht deklariert. Diese Version funktioniert insteressanterweise aber:

class foo
{
   public function foo()
   {
      echo "Ich bin der PHP 4 Konstruktor";
   }
}
 
class bar extends foo 
{
    public function bar()
    {
        parent::__construct();
    }
}
 
new bar();

Kurios, wie ich finde. Aber so ist PHP halt 😉

Grundsätzlich bin ich ja ein großer Fan von require_once() oder auch mal von include_once(). Aber wie bei allen Dingen im Leben sollte man auch hier wissen was man tut.

Üblicherweise wird mit diese Befehlen ja eine Klassendatei oder eine Funktionsbibliothek inkludiert. Letztlich fand ich aber ein Stelle an der Code der direkt ohne Funktionsaufruf ausgeführt werden sollte inkludiert wurde. Da gab es als ein Konstrukt wie dieses hier:

Datei require_me.php:

<?php 
    echo "$i <br />";
?>

Datei require.php:

<?php  
    for ($i = 0; $i < 10; $i ++)
    {
        require_once ('require_me.php');
    } 
    echo "Nach der Schleife: $i"; 
?>

Eigentlich sollte an erwarten, dass die Zahlen von 0 bis 9 und der Wert nach der Schleife ausgegeben werden, wenn man require.php aufruft. Aber weit gefehlt. Die Ausgabe sieht so aus:

0
Nach der Schleife: 10 

Dadurch, dass hier ein require_once() genutzt wurde wird der Code nur ein Mal eingebunden. Beim zweiten und allen weiteren Schleifendurchläufen wird die Datei also nicht noch einmal eingelesen was dazu führt, dass der Code nicht noch einmal ausgeführt werden kann. Die Nutzung eines requires() anstelle von require_once() hätte das verhindert. Wobei auch das ungeschickt ist weil die Datei dann bei jedem Durchlauf eingelesen werden muss. Viel schlauer wäre es den zu inkludierenden Code in einer Funktion zu kapseln und die Funktionsdatei schon vor der Schleife einzulesen. In der Schleife muss dann nur noch nur die Funktion aufgerufen werden.

Bei einem Kunden hatte ich in den letzten Monaten die Aufgabe einen xt:Commerce-Shop mandantenfähig zu machen. Das heißt, es sollten mehrere Shops die unterschiedlich aussehen und unterschiedliche Preise haben auf dieselben Lagerbestände und Artikelinformationen zugreifen und über dasselbe Backend verwaltet werden. Nun ja, genau genommen war das vor vielen Jahren mal ein xt:Commerce der inzwischen aber massiv umgebaut wurde. Nichts desto trotz gab eis eine Eigenheit die mir anfangs ein wenig zu schaffen machte.

Ein Problem war, dass xt:Commerce eine ganze Menge Konstanten nutzt die aus Datenbankeinträgen generiert werden. Das heißt, es gibt eine Tabelle in der der Name der Konstante und der Wert hinterlegt sind. Die Tabelle wird dann komplett ausgelesen und die Konstanten werden einzeln mit define() deklariert:

$result = mysql_query ('SELECT * FROM configuration');
while ($row = mysql_fetch_assoc($result))
{
    define ($row['configuration_key'], $row['configuration_value']);
} 

Innerhalb des Shop-Bereichs ist das kein Problem. Im Administrationsbereich wird das allerdings schnell zum Problem, da beide Shops aber über denselben Administrationsbereich verwaltet werden sollten. Schließlich nutzen sie dieselben Konstantennamen, legen darin aber unterschiedliche Werte ab. Es musste eine Lösung her welche eine einfache und schnelle Lösung ermöglichte.

Um die Sache schnell in den Griff zu bekommen habe ich mich dafür entschieden die ID des Mandanten jeweils an den Namen der Konstante anzuhängen. Innerhalb des jeweiligen Shop-Bereichs ist das einfach machbar, da in jedem Shop die Mandanten-ID in einer Konstante abgelegt ist. Bei der Deklaration einer Konstante kann man also einfach ein Konstrukt wie das hier nutzen:

define ('TENANT_ID', 1);  // ID des Mandaten</code>
$sql = 'SELECT * 
              FROM configuration 
              WHERE tenant_id = '. TENANT_ID
$result = mysql_query ($sql);
while ($row = mysql_fetch_assoc($result))
{
    define ($row['configuration_key'] . TENANT_ID, 
                                     $row['configuration_value']);
} 

Dadurch muss man, wenn man einen neuen Shop für einen Mandanten anlegt nur die ID des Mandaten in der Konfigurationsdatei ändern und alles andere kann gleich bleiben. Natürlich musste auch die Tabelle configuration um die Spalte tenant_id ergänz werden. Alternativ hätte man auch für jeden Mandanten eine eigene Tabelle anlegen können.

Für die Shops ist der Drops damit gelutscht. Der Administrationsbereich ist aber ein Problem. Hier muss mal mit der einen oder er anderen Konstante gearbeitet werden. Dummerweise sieht PHP defaultmäßig keine Möglichkeit vor den Namen einer Konstante beim Zugriff dynamisch zu halten. Das dachte ich zumindest. Aber weit gefehlt, die Funktion constant() macht’s möglich. Anstelle von

echo "Shop Eigentümer: " . SHOP_OWNER_1;

kann man also auch

$tenant_id = 1;
echo "Shop Eigentümer: " . constant('SHOP_OWNER_' . $tenant_id);

schreiben. Durch diese Funktion kann man den Namen der Konstanten also dynamisch generieren. Zugegebenermaßen stellt diese Lösung ein gewisses Maß an Frickelei dar aber wenn man einen bestehenden Shop nachträglich Mandantenfähig machen muss hat man keine große Wahl 😉