Moin!
Datenbankzugriff ist etwas, was keinesfalls zentral gelagert gehört. Es ist ein Aspekt, der unter Umständen sehr flexibel gehandhabt werden muss, denn WELCHE Datenbank wird denn z.B. angesprochen? Und mit welcher Methode? Nur mal als Beispiele: MySQL oder Postgres? Oder MySQL via mysql, mysqli oder PDO-Extension ansprechen? Oder anstelle der relationalen Datenbank was völlig anderes?
Die Datenbank-Klasse ist abstrakt und die Klasse mit den entsprechenden Funktionen (MySQL, MySQLi etc.) wird geladen. Es ist also egal welche DB angesprochen wird, die Funktionsaufrufe bleiben gleich.
Du hast also sowas:
abstract class DB {
abstract public function query($sql);
//...
}
class Mysql_DB extends DB {
public function query($sql) {
mysql_query($sql);
}
}
Da würde ich auch erstmal "suboptimal" rufen wollen.
Zum einen: Die sogenannte Datenbankunabhängigkeit ist, wenn man sie wirklich voll ausprogrammieren wollte, ein schöner Schein! Weil Nichtexistent. Datenbanken sind von Natur aus unterschiedlich, nur deswegen eignet sich die eine für manche Dinge besser, als eine andere. Deswegen ist auch das SQL unterschiedlich, was man zur Nutzung der Features braucht. Will man DB-unabhängig sein, kann man nur die Features nutzen, die in allen DBs vorkommen, oder muss fehlende Features irgendwie per Workaround hinbasteln. Insofern: Du brauchst keine DB-Unabhängigkeit, du entscheidest dich am Anfang für eine existierende Datenbank und benutzt die.
Aber andererseits benutzt du diese DB nur solange, wie sie die Features deines CMS supporten kann. Wenn irgendwann der Fall eintritt, dass die bisherige DB nicht mehr ausreicht, dann willst du die Datenbank doch mal austauschen. Und in so einem Fall ist es viel angenehmer, wenn mindestens mal sämtliche SQL-Querys gesammelt an einem Ort (sprich: Leicht auffindbar in einem bestimmten Typ von Klasse) liegen und dort entsprechend modifiziert bzw. durch neue Querys in einer alternativen Klassenerbfolge ersetzt werden können.
Zweiter Punkt: Die oben genannte Abstrakte Klasse enthält ja keinen ausführbaren Code. Und ich hielte es auch für schwierig, dort solchen Code wirklich unterzubringen, denn der kann ja nur allgemeine Dinge tun, aber keine konkreten DB-Funktionen aufrufen. Allgemeine Dinge sind jedoch vermutlich etwas anderes, als DB-Abfragen vorzunehmen, gehören deshalb nicht in eine DB-Klasse. Ein Interface wäre an dieser Stelle als Alternative für eine abstrakte, codelose Klasse eindeutig vorzuziehen. Und (das ist das I von SOLID) eine Klasse kann mehr als ein Interface implementieren, es ist also möglich und angeraten, keine Mega-Interfaces zu schreiben, sondern für die jeweils beabsichtigten Funktionen ein möglichst kleines Interface vorzusehen.
Als gute Ideengeber würde ich mal die Interfaces der SPL nennen: ArrayAccess definiert vier Methoden, Iterator definiert fünf, und Countable sogar nur eine einzige. Die Kombination aller drei Interfaces erlaubt es, dass ein Objekt sich vollkommen wie ein Array verhalten kann, man aber volle Kontrolle über den Zugriff auf die internen Daten behält.
Ok, und noch ein Grund, warum eine DB-zugreifende Core-Klasse blöd ist: Der Controller hat keinerlei Veranlassung, auf die Datenbank zuzugreifen. Dafür hat er seine Models. Und die Models haben auch keine Veranlassung, die Datenbankzugriffe in sich selbst zu tragen, denn die sind dafür da, die in der DB gespeicherten Daten abzufragen (über eine DB-Klasse) und gegenüber dem Controller bzw. dem View passend zu repräsentieren.
Ok, das leuchtet mir ein. Ich schau mal, dass ich die DB-Klasse in meine Model-Klasse bringe und ob die Core-Klasse überhaupt nötig ist.
Gibt es denn auch Gründe _für_ eine Core-Klasse oder ist das allgemein eine schlechte Idee?
Aus Gründen der Testbarkeit mittels Unit-Tests wird man solche Gott-Klassen immer vermeiden wollen.
Das Problem liegt hierbei meist im benötigten Aufwand, um überhaupt einen Test schreiben zu können. Unit-Tests prüfen die Korrektheit einer Methode, indem die Methode in einer Testumgebung mit Testdaten aufgerufen wird, und das reale Ergebnis mit dem erwarteten Ergebnis verglichen wird. Das erwartete Ergebnis entsteht aus der Anforderung des Programmierers, was die Funktion eigentlich tun soll, und das reale Ergebnis ist das Produkt seiner Programmierkunst, welches der Erwartung hoffentlich nahe kommt.
Im Prinzip rufen Unit-Tests die Methoden "nur mal eben kurz auf". Und es ist für so einen Ansatz extrem hinderlich, wenn diese Methoden nur dann funktionieren, wenn die Testumgebung erst nach einer komplizierten Herstellung von Realität korrekt funktionieren. Viel schöner ist, wenn man den Test mit minimalem Aufwand (ideal: Null Aufwand) schreiben kann. Denn so ein Test ist in der Regel der erste Code, der die programmierte Methode aufruft und benutzt, und es ist außerdem viel einfacher, seine Methoden innerhalb eines eingerichteten und funktionsfähigen Testframeworks aufzurufen und zu testen, anstatt sich zum Ausprobieren immer erst ein kleines test.php zu schreiben und manuell im Browser zu gucken, ob alles funktioniert.
Denn das Problem bei diesem Vorgehen: Die test.php wird beim Ausprobiere beliebig umgewandelt, ohne die vorherigen Testaufrufe zu behalten und deren Ergebnisse zu speichern und später immer wieder maschinell zu prüfen. Das heißt: Wenn man was ändert, könnte man Fehler in etwas einbauen, was früher schon mal korrekt funktioniert hatten - aber weil das Testen dieses Verhaltens weggeworfen wurde, hat man das Problem nicht bemerkt. Und bis es dann soweit ist und das Gefühl von "hier kann irgendwas nicht ganz stimmen" aufkommt, ist die fehlerhafte Änderung schon längst vergessen worden, und die Fehlersuche ist umso aufwendiger.
Langer Rede kurzer Sinn: Wenn du nicht auf Unit-Testing stehst und das ganze für übertriebenen Overhead hältst, dann darfst du auch gern eine zentrale Core-Klasse schreiben und dich damit in dein prophezeihtes Unglück stürzen. ;) Aber auch ohne Unit-Tests ist das Abstandhalten von solchen Gott-Klassen eine förderliche Vorgehensweise, die Abhängigkeiten im Code vermeidet und ihn besser macht.
- Sven Rautenberg