Frage von fragenscheiser, 110

php - static function soll nicht-static function ausführen?

Schön Guten Tag,

Die Klasse sieht folgendermaßen im Grundgerüst aus

class Mysql {

  private $mysqli;

  function __construct(mysqli $mysqli) {
    $this->mysqli = $mysqli;
  }

  protected function _query($query, $select = false) {
    // do something
  }

  public static function query($query, $select = false) {
    $this->_query($query, $select);
  }

}

Nun würde ich das gerne von außen über die public static klasse ausführen bzw. willkürliche querys übergeben, und zwar in Etwa so

$mysql = new Mysql($mysqli);
Mysql::query("SET NAMES 'utf8'");

Leider funktioniert das so nicht, da static offensichtlich nicht ohne Weiteres nicht-static ausführt.

Der Grund warum ich das so ausführbar brauche, ist lediglich, damit ich von jeder x-beliebigen Stelle, und auch aus anderen klassen, die public static function nutzen kann.

Weiß jemand Rat?

Antwort
von MonkeyKing, 70

Es geht nicht denn du kannst aus einer statischen Methode nicht auf eine Nicht-Statische Variable, also in diesem Fall $mysqli zugreifen. Wenn du $mysqlid als statische Variable definierst, wird ein Schuh draus. 

Kommentar von fluffiknuffi ,

(Geht bestimmt irgendwie - über den Atem raubende, die Möglichkeit der reflection API abartig missbrauchende Wege ;) Normalerweise wäre jetzt mein Ehrgeiz geweckt das genauer zu untersuchen aber das ist mit dann doch zu haarsträubend :D )

Kommentar von fragenscheiser ,

Was schlägst Du also vor?

Kommentar von MonkeyKing ,

Singleton, Facade Pattern, das alles scheint mir doch overkill zu sein. Ich würde $mysqli als statische Variable definieren und dann eine statische Funktion init() haben, welche die Datenbankverbindung mit den gewünschten Parametern initialisiert, die verwendest du dann so:

Mysql::init($mysqli);
Mysql::query("SET NAMES 'utf8'");

Oder spricht da aus deine Sicht etwas dagegen?

Kommentar von fragenscheiser ,

Weil einfach einfach einfach ist...

Ich habe es letztendlich auch so umgesetzt, mittels init function wirds nun iniziert und vereinfacht alles, der Rest wurde auf static umgebaut.

Persönlich finde ich es nicht so schön, wenn alles static ist, aber warum es unnötig kompliziert machen, wenn es auch ganz simpel geht.

Vielen Dank für den Gedankenanstoss.

Antwort
von fluffiknuffi, 48

Das Singelton-Pattern wurde bereits erwähnt.

Was du außerdem vermutlich suchst, sind facades. facades sind ein Trick, um genau die beiden Gegensätze static (klassengebunden) und nicht-static (objektgebunden) zu vereinen. Sprich irgendwo gibt es ein mysqli-Objekt, dessen Methoden man aber nach dem Schema MySQLi::query() ansprechen kann. 

Das funktioniert so: Es gibt einmal dein mysqli objekt, dass in einer statischen Variable gespeichert wird (und möglicherweise das Singleton-Pattern umsetzt), und einmal die facade-Klasse, die typischerweise genauso heißt wie das Objekt, aber in einem anderem Nabensraum liegen muss (da es ja nicht zwei gleichnamige Klassen geben kann). Erfolgt ein Methodenaufruf auf eine statische Methode der facade-Klasse, ruft sie einfach die nicht-statische Methode des darunterliegenden Objekts auf (mit den übergebenen Parametern) und gibt den Rückgabewert des Methodenaufrufs zurück.

Das Framework Laravel nutzt facades.

Beispiel: 

 $users = DB::table('users')->get();

(Quelle: https://laravel.com/docs/5.2/queries )

Du kannst facades selber implementieren oder aber eine vorhandene Implementierung nutzen.

Zu guter Letzt soll nicht verschwiegen werden, dass facades nicht unumstritten sind und auch Nachteile haben. Ob dich diese aber überhaupt berühren ist keineswegs gesagt - ich nutze facades sehr gerne.

Kommentar von fragenscheiser ,

Laravel scheint mir schlicht 2much und zu aufwendig alles dahingehend umzuschreiben.

Mir geht es, wie Du schon erkannt hast, um einfache Anwendung der Datenbank, Singleton erscheint mir klar am Vorteilhaftesten zu sein, und ich möchte auch nicht sämtliche Datenbankbasierten Klassen um eine neue Funktion erweitern, das erscheint mir dann wieder als nicht zielführend.

Wie sähe für Dich am obrigen Beispiel eine Singleton-basierte Lösung aus?

Kommentar von fluffiknuffi ,

Ja da stimme ich zu, Laravel ist da mit Kanonen auf Spatzen schießen - war auch eher als Beispiel bzw. weiterführende Lektüre gedacht. Sich "mal eben kurz" ;) in ein ganzes Framework einzuarbeiten würde ich an deiner Stelle auch nicht tun.

Ich persönlich würde nicht das klassische Singleton-Prinzip umsetzen. Soll heißen, deine Datenbank-Klasse würde ich vermutlich so lassen wie sie ist.

Statt dessen würde ich (ich bin allerdings nicht du :) ) an deiner Stelle mir einen IoC-Container bauen - in ganz einfach. Also quasi eine Klasse/Objekt, das Instanzen von Objekten gibt. Und das würde ich dann so konfigurieren, dass es mir immer die selbe Instanz der Datenbank-Klasse gibt, was der Singleton-Idee entspräche (allerdings könnte man natürlich manuell weitere Instanzen der Klasse DB erstellen, was bei einem echten Singleton nicht ginge, aber aus meiner Sicht irrelevant ist).

Ich habe aber so die Ahnung, dass ich mir konkrete Code-Beispiele sparen kann weil das aus deiner Sicht auch eine zu aufwendige Lösung wäre. Was absolut verständlich ist. Ich bin sozusagen in der Fraemwork-Falle gefangen und wenn ich ohne arbeiten muss habe ich die Zwangsstörung mir immer ein Mini-Framework zusammenbauen zu wollen. ;)

Kommentar von fluffiknuffi ,

Ok doch noch Code.

IoC Beispiel generell:

$className = 'Mysql';
$object = $app->getInstance($className);



$app->getInstance:

function getInstance($name) {
  if (isset($this->serviceProviders[$name]) {
    return $this->serviceProviders[$name]->getInstance();
  } else {
    // Simples Instanziieren mittels Autoloading:
    return new $name; // )Bin grad unsicher ob das so ging oder die Syntax anders war)
  }
}

ServiceProvider dienen dazu das Erstellen von Objekten zu übernehmen nicht nur ganz stumpf jedes Mal mit new erstellt werden sollen. Beispiel für Singleton Mysql:

MysqlServiceProvider implements ServiceProviderInterface {
  protected $instance = null;
  function getInstance() {
    if ($instance) return $instance;
    $this->instane = new Mysql;
    return $this->instance;
  }
}

Irgendwie so, wobei so natürlich noch keine Konstruktor-Parameter übergeben werden können aber vermutlich ist das sowieso alles viel zu weit weg von dem was du eigentlich tun möchtest. :D

Kommentar von fragenscheiser ,

Ich Danke Dir für Deine ausführliche Antwort.

Allerdings hast Du Recht, es ist viel zu aufwendig, und leider habe ich mit IoC gar keine Erfahrung, daher habe ich das so nicht umgesetzt.

Dass Du so ausführlich geantwortet hast, kann man gar nicht hoch genug loben. Vielen Dank.

Antwort
von PeterKremsner, 67

Das Problem hier ist, dass du beim static Aufruf einer Funktion keine Instanz der Klasse hast, wenn du keine Instanz hast dann kann $this auch auf nichts zeigen und darum geht das nicht.

Was du benötigst ist das Singleton Pattern.

Durch das hast du maximal eine Instanz der Klasse und diese Instanz wird überall verwendet.

Die Methode selbst ist dann zwar nicht static aber es funktioniert so wie du es möchtest.

Kommentar von fluffiknuffi ,

Glaube aber dem Fragesteller geht es aber auch um den einfachen Zugriff. Wobei ich mich deiner Meinung aber anschließe, dass er das Singleton-Pattern nutzen sollte.

Kommentar von PeterKremsner ,

Naja beim Singleton wäre der Zugriff auch nicht schwieriger, zudem könnte er das ganze noch Kapseln.

static function query()

{

 return self::geInstance()->_query();

}

Kommentar von fluffiknuffi ,

Ja das wäre eine Lösung.

Kommentar von fragenscheiser ,

das obrige Beispiel sieht eigentlich vor, das die Funktionen ansich nichts zurückgeben sollen, das konntest Du natürlich nicht wissen, da ich entsprechenden irrelevanten Code vorher entfernt hatte.

Aber das Singletonbeispiel erscheint sinnvoll, kannst Du Deine Singleton-Lösung noch als ganzes am obrigen Beispiel komplettiert darstellen?

Kommentar von PeterKremsner ,

Du musst das Singleton nur beim ersten mal initialisieren.

Also ein Aufruf von Mysql::getInstance($mysqli) sollte reichen. Danach kannst du die Funktion query static verwenden.

Du solltest aber bei den Funktionen schon mindestens true oder false zurückgeben um einen Fehler zu zeigen, oder eine Exception werfen und Ausnahmebehandlung durchführen. Ansonsten kann das schnell zu einem Fehler führen denn die Funktion query macht nichts bevor nicht vorher Mysql::getInstance($mysqli), aufgerufen wurde.

Solltest du nur die Static Methoden verwenden wollen kannst du auch die getInstance Methode so verändern, dass die Abfrage

if(self::$instance === null)

Nicht durchgeführt wird, dadurch erzwingst du bei jedem Aufruf von getInstance eine neue Instanz für dein $mysqli. Ist aber dann auch kein Singleton mehr. Die alte Datenbankverbindung wird dann aber auch bei jedem Aufruf von getInstance geschlossen.

Es wäre auch diese Methode statt getInstance sinnvoll:

public static function getInstance(mysqli $mysqli = null)
{
if($mysqli !== null or self::$instance !== null)
self::$instance = new Mysql($mysqli);
return self::$instance;
}

Das bewirkt dass du beim Aufruf von getInstance immer dein Singleton bekommst, wenn du aber getInstance ein mysqli Objekt übergibst dann wird ein neues "Singleton" auf Basis der neuen Datenbankverbindung erzeugt.

Was du nimmst hängt davon ab was am besten passt, aber hier ist der Code mit dem reinen Singleton:

class Mysql {

private $mysqli; private static $instance = null;

private function __construct(mysqli $mysqli) {
$this->mysqli = $mysqli;
} public static function getInstance(mysqli $mysqli = null) { if(self::$instance === null) self::$instance = new Mysql($mysqli); return self::$instance; }

protected function _query($query, $select = false) {
// do something
}

public static function query($query, $select = false) { if(self::$instance !== null)
self::$instance->_query($query, $select); else { //Kann keine Abfrage durchführen }
}

}

 Du solltest vielleicht noch beim Zerstören des Mysql Objekts die Datenbankverbindung explizit trennen. Du musst dann aber aufpassen welche Datenbankverbindung du beendest. Wenn du also mit getInstance, so wie im zweiten Codeschnippsel von mir, neue Instanzen auch erzeugen kannst. Dann solltest du in diesem Code zuerst das alte Objekt in $instance zerstören, und dann erst das neue setzen damit der Destructor nicht dein neues $mysqli objekt löscht.

Wie das genau geht lasse ich aber dir zum überlegen ;)

Kommentar von fragenscheiser ,

Ich Danke Dir für Deine ausfürhliche Beschreibung.

Ich habe mich letztendlich doch vorerst für die einfachere Variation entschieden (siehe anderer Post), finde aber die Singleton-Lösung grundsätzlich sehr gut. Eine nachträgliche Einarbeitung dessen wird es sicherlich in der Zukunft geben.

Vielen Dank für Deine Bemühungen.

Antwort
von Alextoexplain, 59

Hello there,

das wäre ganz schlechtes Design so.

Zumal es ja auch kein Problem wäre, wenn du die Datenbank immer initialisierst, wenn du sie eh überall brauchst.

Dann spricht doch nichts dagegen, einmalig eine Instanz zu erzeugen und dann. Möchtest du die Datenbankverbindung auch innerhalb einer anderen Klasse nutzen, dann kannst du sie hier zusätzlich noch im Konstruktor übergeben, am besten via Referenz als &$mysqli, dann musst du die Datenbank nicht nochmal initialisieren.

Ein Singleton wäre eine andere Option, hier ist sowohl die Instanz als auch die Zugriffsfunktion statisch, allerdings ist das mittlerweile kein guter Programmierstil mehr, exzessiv Singletons einzusetzen.

Eine weitere Variante wäre, die Datenbankverbindung mit dem global Schlüsselwort noch überall bekannt zu machen, aber das ist auch sehr schlampig, weil man Variablen nicht global nutzen sollte, sondern nur da, wo man sie auch braucht. Deswegen ist es auch sinnvoll, einen geregelten Zugriff auf die MySQLI Klasse zu haben:

Hoffe das hilft dir weiter.

MfG

Alex

P.S: Umgekehrt kannst du aus einer Instanzmethode immer noch statische Member aufrufen, dies geht über das Selfschlüsselwort. AUs deiner Methode _query() könntest du die öffentliche Methode via self::query() aufrufen,.

Kommentar von fluffiknuffi ,

allerdings ist das mittlerweile kein guter Programmierstil mehr, exzessiv Singletons einzusetzen.

Nun ja so wie du es formuliert hast... "exzessiv", das klingt natürlich negativ besetzt. Aber so ganz allgemein, mir wäre nicht klar, was zumindest hier gegen das Singleton-Pattern spricht. Über dessen Umsetzung kann man sicher diskutieren aber Singleton an sich, wo soll da das Problem sein? Für jeden DB-Zugriff ein eigenes, neues Objekt zu erstellen wäre jedenfalls unsinnig.

Kommentar von fragenscheiser ,

Wie sähe denn beim obrigen Beispiel die Refenzierung Deiner Meinung nach aus?

Antwort
von tWeuster, 50

Dein Suchwort ist "PHP singleton".

Der Aufruf eines Singletons sieht ungefähr so aus: Singleton::init()->query().

Ein Singleton kann (wie es der Name erahnen lässt) nur einmal existieren. Wenn Google betätigt hast wirst du sehen, dass die init Methode (oder wie sie auch immer heißt) entweder das Object erzeugt oder immer das selbe Object zurück liefert.

Ob das jetzt eine gute Idee ist? Wenn dein Code nur aus Singletons besteht, hast du eine funktionale Programmierweise in Klassen gepfuscht. Aber hier und da sind Singletons eine wunderbar effektive Sache.

Kommentar von fluffiknuffi ,

Glaube aber dem Fragesteller geht es aber auch um den einfachen Zugriff. Wobei ich mich deiner Meinung aber anschließe, dass er das Singleton-Pattern nutzen sollte.

Kommentar von fragenscheiser ,

Wie sähe für Dich eine Singleton-basierte Lösung anhand des obrigen Beispiels aus?

Kommentar von tWeuster ,

Wie gesagt, wie man ein Singleton baut kannste dir im Netz raussuchen. Ist keine Zauberrei. Hab eben gesehen dass ein singleton schon hier in den Kommentaren steht. Der Aufruf wäre dann wie folgt:

$mysql = Mysql::init();
$mysql->query("query");

oder falls du das singleton nicht nochmal brauchst

Mysql::init()->query("query");

Init kannst du übergeben was du willst. Auch dein $mysqli. Generell würde ich dem init aber so wenig übergeben wie möglich. Je mehr du ihm übergibst, desto größer wird die Abhängikeit des singletons und desto wahrscheinlicher liegt ein Konzeptfehler vor.

Trotz allem hab ich sogar mal einen mix aus einem singleton und einer Factory gebaut. Da habe ich beim init einen Sprachecode übergeben ("de", "en"). Intern wurden die Instanzen dann in einem Array verwaltet. So würde das Singelton multilingual.

Antwort
von kordely, 25

Mache eine andere Klasse ... oder ein Singleton!

Keine passende Antwort gefunden?

Fragen Sie die Community