OOP - MySQL Klasse sinnvoll? -> Verwendung in anderer Klasse?

Hawkster

Erfahrenes Mitglied
Hallo Ihr lieben,

ich weiß, schon 1000x durchgekaut und SuFu usw.
Vielleicht mangelt es mir etwas am Verständniss oder so, aber ich Frage jetzt einfach nochmal.

Ich versuche mich gerade wieder einmal mit OOP in PHP.

So, meine erste Frage:
Ist es überhaupt sinnvoll eine MySQL Klasse zu schreiben. Ein Teil sagt ja, ein Teil nein.

Falls Sie sinn machen, wie nutzt man das Sinnvoll in anderen Klassen? Da komme ich dann zu dem Singleton... Da lese ich aber wieder das "Singleton" ein schlechter Stil ist usw.

Und nun bin ich echt allmählich verwirrt weil es dann doch kompliziert wird OOP zu festigen wenn es doch so schnell sehr "komplex" wird.

Meine Frage wegen der MySQL Klasse kommt eigentlich als Folgeproblem:
Ich habe eine Klasse geschrieben welche Content von einer Remote-Seite abholen muss (mit Login auf der Remoteseite, Antwortpakete als gzip/chunken usw.) Da ich hier ja schon viele "funktionen" brauche (jedenfalls hab ich es mal so gemacht), habe ich direkt eine klasse daraus gemacht.

Diese ist jetzt das "Basispaket".

Nun möchte ich die Klasse erweitern (vererben?), z.b. würde ich gerne jetzt mit einer weiteren Klasse funktionen machen zum Abspeichern der Daten, Analyse usw. Dazu brauch ich zum einen die (evtl sinnvolle) MySQL Klasse. Und da wirds kompliziert. Eine Klasse schreiben, welche auf einer anderen aufbaut und noch Funktionen der MySQL-Klasse brauch.

Also, mir ist klar das es nicht wirklich so sein kann wie ich es "versuchen" würde, daher will ich einfach vorher um Rat fragen bevor ich wieder diesen OOP-Versuch zum scheitern verurteile.

Mit freundlichen Grüßen,
Hawkster
 
Hi,

also ob als Klasse oder nicht, das musst du schon selbst entscheiden. OOP ist keine Allerweltslösung, sie ist ein Stil, den Sourcecode zu formen.

Wenn du dich dazu entschieden hast, auf die DB mittels einer Klasse zuzugreifen, möchte ich dir zunächst nahe legen, dir MySQLi oder PDO (ich präferiere letzteres) anzusehen. Dort wird dir eigentlich alles als Klasse geschenkt.

Zum Thema Singleton: Natürlich ist ein Singleton guter Stil, zumindest, wenn man es dort einsetzt, wo es Sinn macht. Da es sich um ein Design-Pattern handelt, was sich irgendwann mal jemand ausgedacht hat, kann es eigentlich kein schlechter Stil sein, denn immerhin ist es ein Teil eines Quasi-Standards. Beim Zugriff auf externe Resourcen, bei dem man für jeden Zugriff nicht gleich eine neue Instanz erzeugen lassen will (natürlich aus Performance- evtl. auch aus technischen Gründen), ist Singleton auf jeden Fall das Pattern der Wahl.

Thema Vererbung: Ich weiß nicht, ob es eine gute Idee ist, eine Klasse, die in sich geschlossene Funktionen anbietet, noch mal abzuleiten und darin dann Funktionen zu implementieren, die wieder ganz andere Dinge tun. Da du aber anscheinend noch mitten in der Lern-Phase für das Thema OOP ist, möchte ich hier nicht mit Hämmern wie MVC kommen, obwohl das für dein Vorhaben wohl der bessere Ansatz wäre. Falls dich das interessiert hier mal ein Vorschlag:

Model_Zugriff_auf_Remote_Seite <> Verarbeitungscontroller <> Model_Datenbank

Wenn du willst, kannst du dann noch ein View an den Controller hängen, der dir den Kram dann visualisiert, der erledigt wird.
 
Danke für den Artikel und den Verweis auf die Nachteile. Ich habe sie genau gelesen und bin der Meinung, das genau ein einziger der Punkte auf PHP zutrifft.

Abhängigkeiten zur Singleton-Klasse werden verschleiert, d. h. ob eine Singleton-Klasse verwendet wird, erschließt sich nicht aus dem Interface einer Klasse, sondern nur anhand der Implementierung. Zudem wird die Kopplung erhöht, was Wiederverwendbarkeit und Übersichtlichkeit einschränkt.

Ansonsten sind die Nachteile doch sehr einseitig auf Java bezogen.

In PHP ist ein Singleton immer nur innerhalb einer PHP-Sitzung also vom Start des PHP-Scripts bis zum Ende des Scripts auch tatsächlich vorhanden. Auf andere Sitzungen von konkurrierenden Zugriffen ist es ohnehin nicht so ohne weiteres übertragbar (nicht ohne Aufwand), somit kommt man sich da nicht in die Quere.

Singletons in PHP machen nur Sinn, wenn man diese Punkte bedenkt.


Dann habe ich noch eine Überraschung: PHP ist anscheinend etwas konsequenter, was Zugriffsattribute angeht. Da bekanntermaßen über die ReflectionApi von Java das Security-Konzept ausgehebelt werden kann, habe ich mir gedacht, probier es mal in PHP:

PHP:
<?php
class Singleton
{
  private static $_instance;
  
  private $i;
  
  private function __construct()
  {
    $this->i=0;
  }
  
  public static function getInstance()
  {
    if(self::$_instance == NULL)
      self::$_instance = new self;
    
    return self::$_instance;
  }
  
  public function getI()
  {
    return $this->i;
  }
  
  public function incI()
  {
    $this->i++;
  }
}

echo Singleton::getInstance()->getI() . "\n";

$method = new ReflectionMethod('Singleton', '__construct');

$method->invoke('incI');

echo $method->invoke('getI');

Ergebnis:

Code:
Fatal error: Uncaught exception 'ReflectionException' with message 'Trying to invoke private method Singleton::__construct() from scope ReflectionMethod'
 
Auch
Es besteht die große Gefahr, durch exzessive Verwendung von Singletons quasi ein Äquivalent zu globalen Variablen zu implementieren und damit dann prozedural anstatt objektorientiert zu programmieren.[2]
kann auf PHP bezogen werden... Aber da kommts dann auf den programmierer an..

Was mir noch auffällt: Dein Datenbankobjekt ist schlecht/nicht autauschbar... Wenn in deiner Klasse ständig Datenbank::getInstance()->doSth(); steht, müsstest du das alles von Hand austauschen, wenn du den Datenbankadapter änderst.
 
kann auf PHP bezogen werden... Aber da kommts dann auf den programmierer an..

Richtig, es kommt auf den Programmierer an. Da PHP selbst eigentlich seine Ursprünge in der funktionalen Ecke hat, ist die Versuchung sowieso immer groß. Alles eine Frage der Selbstdisziplin.

Was mir noch auffällt: Dein Datenbankobjekt ist schlecht/nicht autauschbar... Wenn in deiner Klasse ständig Datenbank::getInstance()->doSth(); steht, müsstest du das alles von Hand austauschen, wenn du den Datenbankadapter änderst.

Warum sollte das so sein? Ich implementiere (in den meisten Fällen wirds ohnehin nur Konfiguration sein, da ich PDO verwende) ggf. das Singleton neu. Das ist dann aber schon alles.

Wer beim austauschen des RDBMS seinen kompletten Code umschreiben muss, hat von vornherein was falsch gemacht. Einzelne Queries umschreiben sehe ich noch ein, aber Business-Code? Da ist am Design was ganz anderes schief gelaufen. Ich glaube, du weißt das auch genau selbst und dein Einwand ist nur blabla ;-) Stimmt's?
 
Achso, du trennst die Singletonelogik wieder von der Database-Logik? Ist natürlich auch eine Option...

Mein Einwand ist der, dass du gegen eine konkrete Implementierung programmierst, nämlich gegen die Singletone-Klasse selber.

Kurzes Beispiel von dir geklaut:
PHP:
<?php
class SingletonDB
{
  private static $_instance;
  
  private function __construct()
  {
    $this->i=0;
  }
  
  public static function getInstance()
  {
    if(self::$_instance == NULL)
      self::$_instance = new self;
    
    return self::$_instance;
  }
  
  public function query($sql)
  {
    /* do the query */
  }
}

class Foo {

	public function bar()
	{
		return SingletoneDB::getInstance()->query('SELECT a FROM B');
	}

}

Oder sehe ich das falsch? Kann man das irgendwie anders machen?
 
Hmm, nein, eigentlich würde ich lediglich PDO ableiten, eine finale Klasse nach Singleton-Pattern machen. Etwa so:

PHP:
final class DB extends PDO
{
  private static $_instance;

  private function __construct()
  {
    parent::__construct('mysql:host=localhost;dbname=test', 'user', 'passwort', $parameter_array);
  }

  public static function getInstance()
  {
    if(self::$_instance == null)
      self::$_instance = new self;
    return self::$_instance;
  }

  public function __clone() { throw new Exception('Cloning not allowed'); }
  public function __get($name) { throw new Exception('Getting unknown not allowed'); }
  public function __set($name, $val) { throw new Exception('Setting unknown not allowed'); }
}

Was das binden an eine bestimmte Schnittstelle angeht, gebe ich dir recht. Allerdings ist PDO selbst sowas wie eine Factory und damit recht flexibel was das angeht.

Aber mal ernsthaft: wie oft kommt der Wechsel auf ein anderes DBMS vor?

In Enterprise-Anwendungen würde ich ohnehin ein Framework wie Zend verwenden und dessen Stärken (Bootstrapping, Registry, Resources, etc.) nutzen.

Für eine Gästebuch-Anwendung oder dergleichen genügt obiges Beispiel allemal.

Natürlich kann man das Singleton auch an ein Interface binden - spricht ja nichts dagegen.

Mir geht's da eigentlich nur um den Nutzen, die Datenbank-Verbindung nicht permanent neu zu initialisieren und auf globale Variablen zu verzichten, die dem Risiko des Überschreibens ausgesetzt sind.


EDIT: Upps! Grad mein eigenes Beispiel getestet, das geht so gar nicht... PDO hat einen public Konstruktor, und darf nicht private sein. Also dann doch ohne Ableitung.
 
Zuletzt bearbeitet:
Naja, ein anderes DBMS wird man nicht so häufig verwenden (Ich habe noch nie etwas anderes als MySQL benutzt) [Aber man will ja auf alles vorbereitet sein, siehe hier Punkt 3 ], aber dass man den Adapter verändert, kann doch mal vorkommen. Und wenn du den Namen der Klasse fest in alle deine anderen Klassen reingeschreiben hast (so ist das nunmal bei Singletones), dann wird es sau viel arbeit das alles zu ändern :p
 
Deswegen verwendet man ja auch einen allgemeinen Namen :p

Achso, hier noch ein Schmankerl:

PHP:
<?php
/**
 * Represents one comment in database
 */
class CommentModel
{
	private $id;
	
	public function getId()
	{
		return $this->id;
	}
}

/**
 * Our common database resource container
 */
final class DB
{
	private static $_instance;

	/**
	 * Get the database instance
	 * 
	 * @return PDO
	 */
	public static function getInstance()
	{
		if(self::$_instance == null)
			self::$_instance = new PDO('mysql:host=localhost;dbname=test', 'test', '',
					array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
		return self::$_instance;
	}

	/* Forbid some magic things */
	public function __clone() { throw new Exception('Cloning not allowed'); }
	public function __get($name) { throw new Exception('Getting unknown not allowed'); }
	public function __set($name, $val) { throw new Exception('Setting unknown not allowed'); }
}

try
{
	$stmt = DB::getInstance()->prepare('SELECT id FROM comments WHERE nid = :1');
	$stmt->bindValue(':1', 3);
	$stmt->execute();
	
	$rows = $stmt->fetchAll(PDO::FETCH_CLASS, 'CommentModel');
	var_dump($rows);
}
catch (PDOException $pdoex)
{
	echo $pdoex->getMessage();
}

/**
 * Results:
 * 

array(3) {
  [0] =>
  class CommentModel#3 (1) {
    private $id =>
    string(1) "1"
  }
  [1] =>
  class CommentModel#4 (1) {
    private $id =>
    string(1) "2"
  }
  [2] =>
  class CommentModel#5 (1) {
    private $id =>
    string(1) "3"
  }
}

 */
 
Zurück