Neue Features in cDb (ehemals DB_Contenido) und DB-Adapter

Fragen zur Installation von CONTENIDO 4.9? Probleme bei der Konfiguration? Hinweise oder Fragen zur Entwicklung des Systemes oder zur Sicherheit?
Antworten
xmurrix
Beiträge: 3143
Registriert: Do 21. Okt 2004, 11:08
Wohnort: Augsburg
Kontaktdaten:

Neue Features in cDb (ehemals DB_Contenido) und DB-Adapter

Beitrag von xmurrix » Mi 16. Mai 2012, 01:15

In der Version CONTENIDO 4.9 wurden einige Bereiche im Core überarbeitet, unter anderem auch die Datenbank Klasse cDb sowie die Elternklassen davon.

Neben der teilweisen Adaptierung auf PHP5 wurden auch vorhandene Funktionen erweitert oder neue hinzugefügt. Das Ergebnis ist dabei eine im Vergleich zur vorherigen Version der DB-Klassen sicherere und einfacher zu verwendende DB-Abstraktion.

Im Folgenden möchte ich auf die Neuerungen eingehen.

query()
Die query() Funktion wurde flexibler und lässt sich optional mit verschiedenen Parametern aufrufen.

1. Variante
Während SQL-Statements immer noch wie gewohnt ausgeführt werden können

Code: Alles auswählen

// query(string $statement)

$idlang = 1;
$idart = 2;
$cfg = cRegistry::getConfig();
$db = cRegistry::getDb();
$db->query('SELECT * FROM `' . $cfg['tab']['art_lang'] . '` WHERE idart = ' . cSecurity::toInteger($idart) . ' AND idlang = ' . cSecurity::toInteger($idlang));
if ($db->nextRecord()) {
    echo $db->f('title');
}
2. Variante
Kann man die Funktion auch mit mehreren Parametern aufrufen, wobei der erste Parameter die SQL-Anweisung in Form eines Formatierungsstrings ist und weitere Parameter die Werte, die mit den Formatierungs-Anweisungen in der SQL-Anweisung verarbeitet werden.

Code: Alles auswählen

// query(string $statement [, mixed $args [, mixed $... ]])

$idlang = 1;
$idart = 2;
$cfg = cRegistry::getConfig();
$db = cRegistry::getDb();
$db->query('SELECT * FROM `%s` WHERE idart = %d AND idlang = %d', $cfg['tab']['art_lang'], $idart, $idlang);
if ($db->nextRecord()) {
    echo $db->f('title');
}
Hier wird die Funktion mit 4 Parametern aufgerufen. Dabei ist der erste Parameter die SQL-Anweisung selber und alle weiteren Parameter die zu ersetzenden Werte. Es ist zu beachten, dass die Anzahl der weiteren Parameter auch der Anzahl Formatierungs-Anweisungen im ersten Parameter entspricht.
* %s wird gegen $cfg['tab']['art_lang'] ersetzt
* Das erste %d wird gegen $idart ersetzt
* Das zweite %d wird gegen $idlang ersetzt
Bei dieser Version der Funktion muss man Strings nicht mehr manuell escapen und Werte, die vom Typ integer sein sollen nicht in ein solches umwandeln. Darum kümmert sich die dahinterliegende Funktionalität, die Verwendung von cSecurity::escapeDB() und cSecurity::toInteger() ist nicht mehr nötig, der Code wird weniger und lesbarer

3. Variante
Alternativ kann man query() auch mit 2 Parametern aufrufen, wobei der erste Parameter die SQL-Anweisung in Form eines Formatierungsstrings ist und der zweite Parameter eine indexbasierte Liste mit Werten, die mit den Formatierungs-Anweisungen in der SQL-Anweisung verarbeitet werden.

Code: Alles auswählen

// query(string $statement, array $values)

$idlang = 1;
$idart = 2;
$cfg = cRegistry::getConfig();
$values = array($cfg['tab']['art_lang'], $idart, $idlang);
$db = cRegistry::getDb();
$db->query('SELECT * FROM `%s` WHERE idart = %d AND idlang = %d', $values);
if ($db->nextRecord()) {
    echo $db->f('title');
}
Im Vergleich zur "2. Variante" werden einfach die Werte als List übergeben, alles andere verhält sich identisch.

4. Variante
Letztendlich gibt es noch die Möglichkeit mit sogenannten "named Parametern" zu arbeiten. Dabei wird die Funktion auch mit 2 Parametern aufgerufen. Dabei ist der erste Parameter die SQL-Anweisung und verwendet zu ersetzende Platzhalter. Der zweite Parameter ist eine assoziative Liste mit Werten, die zu ersetzen sind.

Code: Alles auswählen

// query(string $statement, array $values)

$idlang = 1;
$idart = 2;
$cfg = cRegistry::getConfig();
$db = cRegistry::getDb();
$values = array(
    'table_art_lang' => $cfg['tab']['art_lang'],
    'idart' => (int) $idart,
    'idlang' => (int) $idlang
);
$db->query('SELECT * FROM `:table_art_lang` WHERE idart = :idart AND idlang = :idlang', $values);
if ($db->nextRecord()) {
    echo $db->f('title');
}
Bei dieser Version ist zu beachten, dass man integer Werte sicherheitshalber in ein integer castet, da die SQL-Anweisung keine Formatierungs-Anweisungen wie %d enthält. Strings muss man nicht escapen, dies funktioniert wie zuvor.
Die Platzhalter haben einen vorangestellten Doppelunkt ":" und sind ansonsten identisch mit den Schlüsseln im assoziativen Array.
Die Verwendung von "named Parametern" sollte nicht prepared Statements verwechselt werden. DB Treiber, die prepared Statements unterstützen, parsen die SQL-Anweisung einmal und verwenden die geparste Version für den mehrfachen Einsatz. Diese Version führt die Ersetzungen jedes Mal neu aus.

prepare()
Die neue Funktion prepare() ist im Grunde identisch mit der query(), bis auf 2 Punkte.
1. Während query() die Anweisung auch ausführt, liefert prepare() die aufbereitete Anweisung zurück.
2. prepare() kann man nicht mit einem Parameter, also nur mit der SQL-Anweisung, aufrufen.
Manchmal möchte man SQL-Anweisungen nicht direkt ausführen, z. B. vorher loggen. In solchen Fällen ist prepare() genau das Richtige.

1. Variante
Erster Parameter ist die SQL-Anweisung in Form eines Formatierungsstrings ist und weitere Parameter die Werte, die mit den Formatierungs-Anweisungen in der SQL-Anweisung verarbeitet werden.

Code: Alles auswählen

// string prepare(string $statement [, mixed $args [, mixed $... ]])

$idlang = 1;
$idart = 2;
$cfg = cRegistry::getConfig();
$db = cRegistry::getDb();
$sql = $db->prepare('SELECT * FROM `%s` WHERE idart = %d AND idlang = %d', $cfg['tab']['art_lang'], $idart, $idlang);
$db->query($sql);
if ($db->nextRecord()) {
    echo $db->f('title');
}
2. Variante
Aufruf mit 2 Parametern, wobei der erste Parameter die SQL-Anweisung in Form eines Formatierungsstrings ist und der zweite Parameter eine indexbasierte Liste mit Werten, die mit den Formatierungs-Anweisungen in der SQL-Anweisung verarbeitet werden.

Code: Alles auswählen

// string prepare(string $statement, array $values)

$idlang = 1;
$idart = 2;
$cfg = cRegistry::getConfig();
$db = cRegistry::getDb();
$values = array($cfg['tab']['art_lang'], $idart, $idlang);
$sql = $db->prepare('SELECT * FROM `%s` WHERE idart = %d AND idlang = %d', $values);
$db->query($sql);
if ($db->nextRecord()) {
    echo $db->f('title');
}
3. Variante
Aufruf mit 2 Parameters und als "named Parameter" Version. Der erste Parameter ist die SQL-Anweisung und verwendet zu ersetzende Platzhalter. Der zweite Parameter ist eine assoziative Liste mit Werten, die zu ersetzen sind.

Code: Alles auswählen

// string prepare(string $statement, array $values)

$idlang = 1;
$idart = 2;
$cfg = cRegistry::getConfig();
$db = cRegistry::getDb();
$values = array(
    'table_art_lang' => $cfg['tab']['art_lang'],
    'idart' => (int) $idart,
    'idlang' => (int) $idlang
);
$sql = $db->prepare('SELECT * FROM `:table_art_lang` WHERE idart = :idart AND idlang = :idlang', $values);
$db->query($sql);
if ($db->nextRecord()) {
    echo $db->f('title');
}
insert()
Die neue Funktion insert() kann verwendet werden, um einen Datensatz in einer gewünschten Tabelle anzulegen. Es kann als Alternative zum manuellen Erstellen einen INSERT-Statements verwendet werden.

Das manuelle Zusammenstellen eines INSERT-Statements sieht in der Regel folgendermaßen aus:

Code: Alles auswählen

$idcode = 123; // oder mit $db->nextid($cfg["tab"]["code"])
$idcatart = 12;
$idlang = 1;
$idclient = 1;
$code = "<html>... code n' fun ...</html>";
$cfg = cRegistry::getConfig();
$db = cRegistry::getDb();
$sql = "INSERT INTO ".$cfg["tab"]["code"]." (idcode, idcatart, code, idlang, idclient) 
        VALUES (".cSecurity::toInteger($idcode).", ".cSecurity::toInteger($idcatart).",
        '".cSecurity::escapeDB($code, $db)."', ".cSecurity::toInteger($idlang).", 
        ".cSecurity::toInteger($idclient).")";
$db->query($sql);
Mit insert() gibt es dafür eine andere Alternative. Der erste Parameter ist der Name der Tabelle und der zweite Parameter ist ein assoziatives Array in der die Schlüssel die Feldnamen und die Werte die Werte für die Felder sind.

Code: Alles auswählen

// bool insert(string $tablename, array $fields)

$idcatart = 12;
$idlang = 1;
$idclient = 1;
$code = "<html>... code n' fun ...</html>";

$fields = array(
    'idcatart' => (int) $idcatart,
    'idlang' => (int) $idlang,
    'idclient' => (int) $idclient,
    'code' => $code,
);

$cfg = cRegistry::getConfig();

$db = cRegistry::getDb();
$result = $db->insert($cfg['tab']['code'], $fields);
Hier sollte man sicherheitshalber Werte, die vom Typ Integer sein sollen, auch als solches casten. Strings muss man nicht escapen, das wird einem automatisch abgenommen.
Dem Aufmerksamen Leser ist hier bestimmt aufgefallen, dass in diesem Beispiel die Id (idcode) fehlt. In CONTENIDO 4.9 wurde die Verwendung der Sequenz-Tabelle zum Verwalten der Ids der Tabellen entfernt. Mann muss nicht mehr mit $db->nextid() die nächste Id holen, darum kümmert sich nun die Datenbank (MySQL).

buildInsert()
Während insert() die Anweisung zusammenbaut und auch gleich ausführt, liefert buildInsert() die zusammengebaute Anweisung zurück. Man kann also das Statement vorher anderweitig verwenden, z. B. loggen.

Code: Alles auswählen

// string buildInsert(string $tablename, array $fields)

$idcatart = 12;
$idlang = 1;
$idclient = 1;
$code = "<html>... code n' fun ...</html>";

$fields = array(
    'idcatart' => (int) $idcatart,
    'idlang' => (int) $idlang,
    'idclient' => (int) $idclient,
    'code' => $code,
);

$cfg = cRegistry::getConfig();

$db = cRegistry::getDb();
$sql = $db->buildInsert($cfg['tab']['code'], $fields);
$result = $db->query($sql);
update()
Die neue Funktion update() ist zum Aktualisieren eines vorhandenen Datensatzes gedacht. Es kann als Alternative zum manuellen Erstellen einen UPDATE-Statements verwendet werden.

Das manuelle Zusammenstellen eines UPDATE-Statements sieht in der Regel folgendermaßen aus:

Code: Alles auswählen

$idcode = 123;
$code = "<html>... more code n' fun ...</html>";
$cfg = cRegistry::getConfig();
$db = cRegistry::getDb();
$sql = "UPDATE ".$cfg["tab"]["code"]." SET code = '".cSecurity::escapeDB($code, $db)."' 
        WHERE idcode = " . cSecurity::toInteger($idcode);
$db->query($sql);
Mit update() gibt es dafür eine andere Alternative. Der erste Parameter ist der Name der Tabelle und der zweite Parameter ist ein assoziatives Array in der die Schlüssel die Feldnamen und die Werte die Werte für die Felder sind. Außerdem gibt es einen dritten Parameter der ein assoziatives Array ist und die WHERE-Bedingungen enthält.

Code: Alles auswählen

// bool update(string $tablename, array $fields, array $where)

$idcode = 123;
$code = "<html>... more code n' fun ...</html>";

$fields = array(
    'code' => $code,
);
$where = array(
    'idcode' => (int) $idcode
);

$cfg = cRegistry::getConfig();
$db = cRegistry::getDb();
$result = $db->update($cfg['tab']['code'], $fields, $where);
Auch hier sollte man sicherheitshalber Werte, die vom Typ Integer sein sollen, auch als solches casten. Strings muss man nicht escapen.
Hat der dritte Parameter mehrere WHERE-Bedingungen, werden diese mit AND verknüpft.

buildUpdate()
Während update() die Anweisung zusammenbaut und auch gleich ausführt, liefert buildUpdate() die zusammengebaute Anweisung zurück.

Code: Alles auswählen

// string buildUpdate(string $tablename, array $fields, array $where)

$idcode = 123;
$code = "<html>... more code n' fun ...</html>";

$fields = array(
    'code' => $code,
);
$where = array(
    'idcode' => (int) $idcode
);

$cfg = cRegistry::getConfig();
$db = cRegistry::getDb();
$sql = $db->buildUpdate($cfg['tab']['code'], $fields, $where);
$result = $db->query($sql);

toArray()
Dei neue Funktion toArray() liefert den aktuellen Datensatz, als Array zurück. Dabei ist es möglich den Datensatz als assoziatives und/oder indexiertes Array zurückzuliefern. Per default wird ein assoziatives Array zurückgeliefert.

Code: Alles auswählen

// string toArray(string $fetchmode)

$idlang = 1;
$idart = 2;
$cfg = cRegistry::getConfig();
$db = cRegistry::getDb();
$sql = $db->prepare('SELECT * FROM `%s` WHERE idart = %d AND idlang = %d', $cfg['tab']['art_lang'], $idart, $idlang);
$db->query($sql);
if ($db->nextRecord()) {
    $assocRs = $db->toArray(); // oder mit Parameter cDbDriverHandler::FETCH_ASSOC
    echo "<pre>\$assocRs: " . print_r($assocRs, true) . "<pre>";

    $indexedRs = $db->toArray(cDbDriverHandler::FETCH_NUMERIC);
    echo "<pre>\$indexedRs: " . print_r($indexedRs, true) . "<pre>";

    $bothdRs = $db->toArray(cDbDriverHandler::FETCH_BOTH);
    echo "<pre>\$bothdRs: " . print_r($bothdRs, true) . "<pre>";
}

toObject()
Dei neue Funktion toObject() liefert den aktuellen Datensatz, als Objekt zurück das eine Instanz von stdClass ist (Standard Klasse in PHP). Die Eigenschaften des Objekts entsprechen den Feldnamen der Tabelle.

Code: Alles auswählen

// string toObject(string $fetchmode)

$idlang = 1;
$idart = 2;
$cfg = cRegistry::getConfig();
$db = cRegistry::getDb();
$sql = $db->prepare('SELECT * FROM `%s` WHERE idart = %d AND idlang = %d', $cfg['tab']['art_lang'], $idart, $idlang);
$db->query($sql);
if ($db->nextRecord()) {
    $rs = $db->toObject();
    echo "<pre>\$rs: " . print_r($rs, true) . "<pre>";

    echo "<pre>idartlang: " . $rs->idartlang . "<pre>";
    echo "<pre>title: " . $rs->title . "<pre>";
    echo "<pre>author: " . $rs->author . "<pre>";
}

escape()
Mit der neuen Funktion escape() kann man Variablen vom Typ String bei Bedarf escapen. Diese Funktion ist eine Alternative zu cSecurity::escapeDB().

Code: Alles auswählen

// string escape(string $str)

$db = cRegistry::getDb();

$code = "<html>... more code n' fun ...</html>";

$escapedCode = $db->escape($code);

// Zuvor war dies folgendermaßen möglich

$db = cRegistry::getDb();

$code = "<html>... more code n' fun ...</html>";

$escapedCode = cSecurity::escapeDB($code, $db);

Das waren die wichtigsten Neuerungen in den Datenbank-Klassen cDb und Eltern-Klassen. Entwickler haben nun mehr Möglichkeiten, um SQL-Anweisungen zu generieren, zum Teil ist das Erstellen der SQL-Anweisungen einfacher sowie lesbarer geworden und der Sicherheitsaspekt wurde zum Teil in die DB-Adapter verlagert.
CONTENIDO Downloads: CONTENIDO 4.10.1
CONTENIDO Links: Dokumentationsportal, FAQ, API-Dokumentation
CONTENIDO @ Github: CONTENIDO 4.10 - Mit einem Entwicklungszweig (develop-branch), das viele Verbesserungen/Optimierungen erhalten hat und auf Stabilität und Kompatibilität mit PHP 8.0 bis 8.2 getrimmt wurde.

idea-tec
Beiträge: 1242
Registriert: Do 19. Sep 2002, 14:41
Wohnort: Dichtelbach
Kontaktdaten:

Re: Neue Features in DB_Contenido und DB-Adapter

Beitrag von idea-tec » Mi 16. Mai 2012, 07:23

Hallo Murat, danke dafür, TOP!
MfG, Karsten
Nicht Können bedeutet nicht, dass man etwas nicht beherrscht, sondern lediglich, dass man sich nicht traut es zu tun ;-)
| Internet | Ihr Logo deutschlandweit auf T-Shirts |
Diplomatie: Jemanden so in die Hölle zu schicken, dass er sich auf die Reise freut!!! ;-)

Seelauer
Beiträge: 186
Registriert: So 22. Jan 2006, 21:03
Wohnort: Mal da, mal da
Kontaktdaten:

Re: Neue Features in DB_Contenido und DB-Adapter

Beitrag von Seelauer » Fr 18. Mai 2012, 07:46

Vielen Dank Murat für die detailierten und verständlichen Erläuterungen. Sehr anschaulich !
Guten Gruß
Seelauer.

kummer
Beiträge: 2423
Registriert: Do 6. Mai 2004, 09:17
Wohnort: Bern, Schweiz
Kontaktdaten:

Re: Neue Features in DB_Contenido und DB-Adapter

Beitrag von kummer » Fr 18. Mai 2012, 16:31

werden die werte beim aufruf von query gebunden? oder handelt es sich bloss um eine substituation? das hat ja dann direkten einfluss auf die anwendung. bei bindungen bestehen einschränkungen z.b. bei anwendung von IN, welche bei einer substitution funktionieren würde. umgekehrt bleibt bei der substitution das injektionsproblem bestehen, welches durch die bindung aufgelöst werden würde. das zugrunde liegende verfahren und dessen kenntnis ist also von bedeutung. eine weitere auswirkung besteht in der anwendung der prepare-methode. ohne bindung würde prepare wenig nützen, da ich dann ja keine neuen werte binden und die vorbereitete abfrage absetzen könnte.
aitsu.org :: schnell - flexibel - komfortabel :: Version 2.2.0 (since June 22, 2011) (jetzt mit dual license GPL/kommerziell)

xmurrix
Beiträge: 3143
Registriert: Do 21. Okt 2004, 11:08
Wohnort: Augsburg
Kontaktdaten:

Re: Neue Features in DB_Contenido und DB-Adapter

Beitrag von xmurrix » Fr 18. Mai 2012, 21:29

@kummer:
Es ist eine reine Ersetzung (Substitution) und ist nicht gleichzusetzen mit binding. Die prepare-Methode funktioniert auch rein nach dem Prinzip der Ersetzung.
CONTENIDO Downloads: CONTENIDO 4.10.1
CONTENIDO Links: Dokumentationsportal, FAQ, API-Dokumentation
CONTENIDO @ Github: CONTENIDO 4.10 - Mit einem Entwicklungszweig (develop-branch), das viele Verbesserungen/Optimierungen erhalten hat und auf Stabilität und Kompatibilität mit PHP 8.0 bis 8.2 getrimmt wurde.

ravi
Beiträge: 54
Registriert: So 6. Aug 2006, 14:39
Kontaktdaten:

Re: Neue Features in DB_Contenido und DB-Adapter

Beitrag von ravi » So 27. Mai 2012, 23:19

Finde ich eine tolle neue Funktionalität. Sehr einfach zu verwenden. :)

Tobias Braune
Beiträge: 5
Registriert: Fr 20. Jul 2012, 01:42
Kontaktdaten:

Re: Neue Features in DB_Contenido und DB-Adapter

Beitrag von Tobias Braune » Fr 20. Jul 2012, 01:46

Hey,
ich finde das interessant und auch lohnenswert.
Habt ihr einmal über einen SQL-Builder nachgedacht?
Also Behaviour um sich die Statements erstellen zu lassen.

Gruß Tobi

xmurrix
Beiträge: 3143
Registriert: Do 21. Okt 2004, 11:08
Wohnort: Augsburg
Kontaktdaten:

Re: Neue Features in DB_Contenido und DB-Adapter

Beitrag von xmurrix » Mi 25. Jul 2012, 21:37

Hallo Tobias,
Tobias Braune hat geschrieben:...Habt ihr einmal über einen SQL-Builder nachgedacht?
Also Behaviour um sich die Statements erstellen zu lassen.
...
danke für dein Vorschlag, ich kann mir vorstellen, dass ein SQL-Builder kommen könnte, um die manuell erstellten SQL-Statements entgültig abzuschaffen.

Eigentlich war die Idee, die vorhandene Basis etwas zu erweitern und keine größeren Features zu integrieren. So ein SQL-Builder ist nicht ohne, ich meine man muss viel Zeit für die Entwicklung investieren.

Gruß
xmurrix
CONTENIDO Downloads: CONTENIDO 4.10.1
CONTENIDO Links: Dokumentationsportal, FAQ, API-Dokumentation
CONTENIDO @ Github: CONTENIDO 4.10 - Mit einem Entwicklungszweig (develop-branch), das viele Verbesserungen/Optimierungen erhalten hat und auf Stabilität und Kompatibilität mit PHP 8.0 bis 8.2 getrimmt wurde.

marcus.gnass_4fb
Beiträge: 87
Registriert: Do 26. Apr 2012, 23:02
Kontaktdaten:

Re: Neue Features in cDb (ehemals DB_Contenido) und DB-Adapt

Beitrag von marcus.gnass_4fb » Di 19. Nov 2013, 12:18

Dieser Beitrag ist nun auch in der Entwickler-Dokumentation enthalten: https://docs.contenido.org/display/COND ... b+features.

Antworten