mmofacts.com

Data Access Object

gepostet vor 17 Jahre, 10 Monate von None
Ich habe hier mal ein Data Access Object (kurz DAO) für den Benutzerzugriff erstellt. Diese verwendet die MySQLi-Bibliothek zum Datenzugriff. Da im eigentlichen Code nur mehr die DAO-Befehle (bzw. get/set-Befehle) verwendet werden, kann man das DAO einfach auf die normale MySQL-Bibliothek oder sogar auf ein anderes DB-System umstellen, oder wenn man lustig ist sogar auf XML. VOn wo die Benutzer ausgelesen werden interresiert nur das DAO und kann dem eigentlichen Entwickler egal sein. Zum SQL-Debuging verwende ich eine eigene Funktion namens sql_die() Falls das DAO verbesserungswürdig ist, einfach schreiben. Verbesserungsvorschläge und auch Kritiken höre ich gerne. Was jetzt noch fehlt ist createUser und deleteUser, welche ich aber noch hinzufügen werde. Die erforderliche Version ist PHP 5.
includes/dao.php
// Data Access Objects

class DaoManager
{
private $daos = array(
'UserDao' => 'SqlUserDao'
);

private $inst = array();

public function getDao($iface)
{
if(!isset($this->inst[$iface])) {
$this->inst[$iface] = new $this->daos[$iface]();
}

return $this->inst[$iface];
}
}

// User Data Access Object START
interface UserDao
{
public function getById($id);
public function getByLogin($name, $pass);

public function checkUser(User &$user);
public function createUser(User &$user);
public function updateUser(User &$user);
public function deleteUser(User &$user);
}
class SqlUserDao implements UserDao
{
private $mysqli;

public function __construct()
{
$this->mysqli = new mysqli('mysql*', 'username', 'password', 'username0*', 3306, '/tmp/mysql_*.sock');
if(mysqli_connect_errno()) {
die(sprintf('#%d %s', mysqli_connect_errno(), mysqli_connect_error()));
}
}

public function __destruct()
{
$this->mysqli->close();
}

public function getById($id)
{
$sql = "SELECT user_id, user_name, user_pass, user_mail
FROM users
WHERE user_id = %d";

$sql = sprintf($sql,
intval($id)
);

$result = $this->mysqli->query($sql)
or sql_die($sql, $this->mysqli->errno, $this->mysqli->error, __LINE__, __FILE__);

return $this->extractResult($result);
}

public function getByLogin($name, $pass)
{
$sql = "SELECT user_id, user_name, user_pass, user_mail
FROM users
WHERE user_name = '%s'
AND user_pass = '%s'";

$sql = sprintf($sql,
$this->mysqli->real_escape_string($name),
$this->mysqli->real_escape_string($pass)
);

$result = $this->mysqli->query($sql)
or sql_die($sql, $this->mysqli->errno, $this->mysqli->error, __LINE__, __FILE__);

return $this->extractResult($result);
}

public function checkUser(User &$user)
{
$ec = 0;

if($user->getId() != 0) {
$ec = 1;
}

$sqlName = "SELECT user_id
FROM users
WHERE user_name = '%s'";

$sqlName = sprintf($sqlName,
$this->mysqli->real_escape_string($user->getName())
);

$resultName = $this->mysqli->query($sqlName)
or sql_die($sql1, $this->mysqli->errno, $this->mysqli->error, __LINE__, __FILE__);

if($resultName->num_rows != 0) {
$ec += 2;
}

$sqlMail = "SELECT user_id
FROM users
WHERE user_mail = '%s'";

$sqlMail = sprintf($sqlMail,
$this->mysqli->real_escape_string($user->getMail())
);

$resultMail = $this->mysqli->query($sqlMail)
or sql_die($sqlMail, $this->mysqli->errno, $this->mysqli->error, __LINE__, __FILE__);

if($resultMail->num_rows != 0) {
$ec += 4;
}

return $ec;
}

public function createUser(User &$user)
{
$sql = "INSERT INTO users
SET user_name = '%s', user_pass = '%s', user_mail = '%s'";

$sql = sprintf($sql,
$this->mysqli->real_escape_string($user->getName()),
$this->mysqli->real_escape_string($user->getPass()),
$this->mysqli->real_escape_string($user->getMail())
);

$this->mysqli->query($sql)
or sql_die($sql, $this->mysqli_errno, $this->mysqli->error, __LINE__, __FILE__);

$user->setId($this->mysqli->insert_id);
}

public function updateUser(User &$user)
{
$sql = "UPDATE users
SET user_name = '%s', user_pass = '%s', user_mail = '%s'
WHERE user_id = %d";

$sql = sprintf($sql,
$this->mysqli->real_escape_string($user->getName()),
$this->mysqli->real_escape_string($user->getPass()),
$this->mysqli->real_escape_string($user->getMail()),
$user->getId()
);

$this->mysqli->query($sql)
or sql_die($sql, $this->mysqli->errno, $this->mysqli->error, __LINE__, __FILE__);
}

public function deleteUser(User &$user)
{
$sql = "DELETE FROM users
WHERE user_id = %d";

$sql = sprintf($sql,
$user->getId()
);

$this->mysqli->query($sql)
or sql_die($sql, $this->mysqli->errno, $this->mysqli->error, __LINE__, __FILE__);

$user->destroy();
}

private function extractResult($result)
{
if($result->num_rows != 1) {
return false;
}

$user = new User();
$row = $result->fetch_assoc();
$user->setId($row['user_id']);
$user->setName($row['user_name']);
$user->setPass($row['user_pass']);
$user->setMail($row['user_mail']);

return $user;
}
}
class User
{
private $id = 0;
private $name = '';
private $pass = '';
private $mail = '';

public function __call($method, $parameters)
{
$what = substr($method, 0, 3);
$var = strtolower(substr($method, 3));

if($what == 'set') {
$this->{$var} = $parameters[0];
} elseif($what == 'get') {
return $this->{$var};
}
}

public function setId($id)
{
if($this->id == 0)
{
$this->id = intval($id);
}
}

public function destroy()
{
$this->id = 0;
$this->name = '';
$this->pass = '';
$this->mail = '';
}
}
// User Data Access Object END
?>
includes/functions.php
// SQL die() Message

function sql_die($sql, $errno, $error, $line, $file)
{
$message = sprintf("%s\n#%d %s\n#%d in %s", $sql, $errno, $error, $line, $file);
$message = nl2br($message);
die($message);
}
?>
use.php
include 'includes/functions.php';

include 'includes/dao.php';
$daoManager = new DaoManager(); // intialisiert den DaoManager
$userDao = $daoManager->getDao('UserDao'); // ein UserDao wird erstellt.
$user = $userDao->getById(1); // oder $user = $userDao->getByLogin('username', 'password'); womit der Login Fall auch gelöst ist. Bei einem nich existiernden User wird False zurückgegeben.
echo $user->getName(); // gibt den Benutzernamen aus.
$user->setPass(md5($user->getPass())); // mache aus einem Plaintext (in diesem Beispiel) ein MD5 verschlüsseltes Passwort.
$userDao->updateUser($user); // dies nicht vergessen, da sonst die Änderungen über die get/set Klasse $user nicht übernommen werden.
$userFoo = new User(); // Ein neuer Benutzer wird erstellt. Id darf nicht gesetzt werden, da sonst die $userFoo Instantz ungültig ist.
$userFoo->setName('Foo'); // Name setzen.
$userFoo->setPass(md5('foo')); // Passwort setzen.
$userFoo->setMail('[email protected]'); // E-Mail Adresse setzen.
if($userDao->checkUser($userFoo) != 0) { // User überprüfen. Werte können adiert zürückgegeb werden.
die('Ungültig'); // 0: Alles OK, 1: Id gesetzt, 2: Name existiert, 3: 1+2, 4: E-Mail-Adresse exisitiert, 5: 1+4, 6: 2+4, 7: 1+2+4;
}
$userDao->createUser($userFoo); // Benutzer Foo erstellen. Hier wird auch die Id erzeugt.
echo $userFoo->getId(); // Erzeugte Id ausgeben.
$userDao->deleteUser($userFoo); // Foo wieder löschen.
?>
gepostet vor 17 Jahre, 10 Monate von None
createUser() und deleteUser() wurden hinzugefügt, für die *User()-Methoden, wird der $user (eine Instanz der Klasse User) per Referenzt übergeben, was somit Speicherplatz (und wahrscheinlich somit Performance) spart, da die Klasse nicht komplett kopiert werden muss. Auch kann sie direkt im DAO bearbeitet werden, wie es mit der Id in createUser() geschieht.
gepostet vor 17 Jahre, 10 Monate von Agmemon
Erstmal Respekt, dass Du dich nach der Diskussion mit der DAO Geschichte so intensiv auseinander gesetzt hast, und der Code sieht sehr vernünftig aus. Wäre es aber nicht einfacher ein fertiges O/R-Mapping zu verwenden? Denn der Code-Umfang ist ja nicht gerade gering, wie ich sehe.
gepostet vor 17 Jahre, 10 Monate von None
Fehlen tut halt noch die checkUser(). Einfacher wäre es schon ein fertiges zu verwenden, aber mir macht es Spaß sowas selber zu schreiben, bzw. erweitere ich damit meine Fähigkeiten. Und ich glaube ein Projekt mit DAOs ist auch bei späterer Jobsuche nicht von Nachteil.
gepostet vor 17 Jahre, 10 Monate von Kampfhoernchen
Wir haben mit solchen DB-Zugriffswrappern sehr gute Erfahrung gemacht. Wir unterscheiden dabei jewails noch zwischen UserList (sozusagen ein Array aller Benutzer mit Funktionen, die man bei mehreren Benutzern gleichzeitig ausführen kann) und der Klasse User selbst.
gepostet vor 17 Jahre, 10 Monate von TheUndeadable
> Wäre es aber nicht einfacher ein fertiges O/R-Mapping zu verwenden?
Kennst du einen guten O/R-Mapper in PHP? Das was ich bisher gesehen hatte, hat mich nicht überzeigt.
gepostet vor 17 Jahre, 10 Monate von Toby
Ich hab da mal was ähnliches gebastelt, weil ich eigentlich kein SQL schreiben will.
Kompliziertere Abfragen (alles was über einen simplen Select hinausgeht, also auch Joins!) will ich alle als Views in die DB verlagern.
Daher ist dann sowas wie das hier rausgekommen:
oh-trac.tobyf.de/trac/browser/frontend/trunk/libs/db/dataobject.inc.php
Das ganze gibt es nochmal als ListObject, das dann eben Listen erstellen kann. Aus einer Liste kann ich mir dann auch ein bestimmtes DataObject ziehen. Inserts gehen nur auf die Liste (eigentlich ist Liste falsch, Table wäre eher treffend).
Gedacht ist es auch so, das ich diese generischen Klassen ableite und dann entsprechend abändere, indem ich entweder Funktionen überschreibe, wenn ich andere Funktionalität brauche oder weitere Klassen bereitstelle.
oh-trac.tobyf.de/trac/browser/frontend/trunk/libs/db/user.inc.php
ist dafür ein Beispiel.
Habs leider nicht ganz geschafft, das die Klassen sich selber alle notwendigen Daten besorgen, leider.
gepostet vor 17 Jahre, 10 Monate von Kampfhoernchen
Das Prado-Framework soll ganz gut sein. Habs aber noch nicht ausprobiert.
gepostet vor 17 Jahre, 9 Monate von Agmemon
Namentlich kenne ich Propel und EZPDO, habe beide aber noch nicht benutzt. Propel basiert auf Apache Torque und wird bei Tigris gehostet. So vom ersten Blick auf die Doku, scheint es einen guten Eindruck zu machen.
gepostet vor 17 Jahre, 9 Monate von None
Hab jetzt noch die letzte fehlende Methode checkUser() hinzugefügt.
gepostet vor 17 Jahre, 9 Monate von riki1512
Mein DAO-Konzept ist ein wenig (nicht prinzipiell) anders, jetzt ist aber spät, daher erst mal nur so viel:

...
private $mysqli;
...
public function __construct()
{
$this->mysqli = new mysqli('mysql*', 'username', 'password', 'username0*', 3306, '/tmp/mysql_*.sock');
...
}

public function __destruct()
{
$this->mysqli->close();
}

public function getById($id)
{
...
}
...
Diese Eigenschaften/Methoden kommen in jedem Tabellen-DAO vor und sollten daher in einer Basisklasse definiert sein (SqlDao), von dieser werden alle TabellenDAOs abgeleitet. Ist die DB-Verbindungsklasse nur auf MySQL ausgelegt und sind die SQL-statements MySql-Dialekt lastig, sollten die SqlDaos dann auch MySqlDaos heissen.
gepostet vor 17 Jahre, 9 Monate von Agmemon
In beiden Varianten ist der Aufbau zur Datenbank im Konstruktor geregelt. Wäre es nicht sinnvoller, die DB-Verbindung noch mal in einer eigenen Klasse zu Kapseln, als Singelton. Und die Instanz dann jeweils als Attribut in den Klassen zu nutzen?
gepostet vor 17 Jahre, 9 Monate von riki1512
Original von Agmemon
In beiden Varianten ist der Aufbau zur Datenbank im Konstruktor geregelt. Wäre es nicht sinnvoller, die DB-Verbindung noch mal in einer eigenen Klasse zu Kapseln, als Singelton. Und die Instanz dann jeweils als Attribut in den Klassen zu nutzen?

Sinnvoll wäre es wohl - habe mal gelesen, dass wenn man sich bei der Art der Beziehung zwischen 2 Klassen nicht entscheiden kann, soll man immer die 'sanftere' Art wählen (also die weniger fest verdrahtete).
Theoretisch also wohl sinnvoll, nur hat obige Methode den praktischen Vorteil, dass ich mit $userDao = new UserDao() bereits auch die DB-Verbindung unter Dach und Fach hab', und nicht vorher eine Art $dbConn = new DbConn() und dann $userDao = new UserDao($dbConn) oder $userDao = new UserDao(new DbConn()) oder ähnliches machen muss. Singleton finde ich hier nicht soo angebracht, mysql_pconnect kümmert sich ja von Haus aus schon um die Einzigartigkeit der Verbindung. Außerdem wird Singleton angesichts der kurzen Lebensdauer von PHP-Objekten ohnehin nicht viel Einsatz finden, leider gibt es hier nicht wie in J2EE einen Application-Scope, sondern lediglich den Session-Scope ($_SESSION)- und der fristet sein Dasein serialisiert in Dateien - da ist dann mit Resource-Typen sowieso Sense. Aber vielleicht lieben wir PHP ja gerade wegen dieser Einfachkeit
Ach ja, straft mich Lügen wenn das in PHP5 längst alles anders ist, he he, habe mich erst vor kurzem dazu überwinden können, das ganze PHP5-Gedöns mit den ganzen gd-libs und unterlibs zu compilieren...
gepostet vor 17 Jahre, 9 Monate von mifritscher
Zum Thema App-weite Speicherung: schau dir mal apc an, neben einem Cache für php-Dateien hat es einen Serverweiten Cache für Variablen ( php.net/apc )
gepostet vor 17 Jahre, 9 Monate von riki1512
Original von mifritscher
Zum Thema App-weite Speicherung: schau dir mal apc an, neben einem Cache für php-Dateien hat es einen Serverweiten Cache für Variablen ( php.net/apc )

Uiii, eine sehr feine Sache. Schon mal ausprobiert ? Schnell ? Zuverlässig ?
Finde mal wieder keine Doku zwecks Einbindung in das PHP Modul.
gepostet vor 17 Jahre, 9 Monate von woodworker
naja apc speichert auch nur serialisierte daten und das auch nur über dei tiefe von einem level
gab mal einen ansatz namens SRM aber so wie ich das bisher mitbekommen habe ist das eingeschalfen.
also ne extra PHP Engine in der Objekte laufen und auf die man sich dann verbinden kann
    /* Starting a connection to the SRM daemon */

$srm = new SRM ('localhost', 7777);
$app = new SRMApp ($srm, 'Uptime');
echo $app->new_uptime ();
echo $app->uptime."\n";
$app->count2 = 100;
?>
SRM Userdoc
gepostet vor 17 Jahre, 9 Monate von riki1512
Jetzt wo du das sagst: Man könnte ja selbst einen PHP-Thread per Kommandozeile starten, der nichts anderes tut. Austauschen könnte man das Zeugs per json (oder einfach PHP serialisiert). Also das Skript wartet in einer Endlosschleife auf Verbindungen und speichert die einkommenden json-Strings einfach in einem assoziativen Array unter bestimmter ID, die kann man ja auch vorher angeben. Oder das Austauschformat ist einfach ein Objekt der Klasse "AppScopeObject", mit zwei Membern: ID und zu speichernde Variable, das kann dann alles sein, tja Mist, außer wieder Resource...
Was war denn eigentlich das Thema ?
Na ja, also wenn man es braucht, ist in ein paar Zeilen gemacht. Hab mal eine Script geschrieben, das in einer Endlosschleife alle 10 Sekunden Prozesse auf Dauer, Memoryeinnahme und CPU-Tätigkeit checkt und bei Bedarf killt, lief problemlos 1,5 Jahre am Stück (Müsste mal wieder nachschauen...), also da gabs keine Probleme mit Kommandozeilen-PHP.
[Übel wie viele Tippfehler ich immer drin hab]

Auf diese Diskussion antworten