Ik wil graag al mijn mysql query's door een functie laten lopen zodat ik deze in de toekomst altijd kan aanpassen en voor verschillende database soorten dezelfde functies kan gebruiken,
echter als ik onderstaande class uitvoer krijg ik een oneindige loop met alleen de eerste tabel rij in mijn script, ik heb al de halve avond lopen zoeken en zag ook hier op het forum een vraag waarin Wijnand destijds een class maakte waarin hij de fetch_assoc(); deed returnen (Deze vraag) en dan de laatste reactie
Wat doe ik verkeerd of is het gewoon niet mogelijk op deze manier?
of moet ik dit niet via oop proberen?
class mydb extends mysqli{
public function __construct($user,$password,$name,$host='localhost') {
$this->connection = new mysqli($host, $user, $password, $name);
if ($this->connection->connect_errno) {
return $this->connection->connect_error;
}
}
public function _setQuery($sql) {
$this->sql = $sql;
}
public function _getQuery() {
return $this->sql;
}
public function getRows() {
$result = $this->connection->query($this->sql);
return $result->fetch_assoc();
}
}
Thomas - 16/01/2014 21:20 (laatste wijziging 16/01/2014 22:05)
Moderator
Je voert met getRows de hele tijd dezelfde query opnieuw uit.
Ook onthoud je je "resultset pointer" niet. Die wijst dus elke keer naar het begin van je de rij van resultaten (van de query die je telkens opnieuw uitvoert). Deze "pointer" moet onthouden worden, dit kun je doen door deze te retourneren. Deze "pointer" is van het type MySQLi_Result. Ik heb toevallig onlangs een OOP versie van MySQLi geimplementeerd, ik zal de code + een voorbeeld er zometeen bijplakken.
EDIT: Dat hele transaction gebeuren heb ik nog niet uitgebreid getest, maar het idee is wel duidelijk denk ik?
<?php
class DatabaseMySQLi
{
protected $connection;
protected $transactionStarted;
public function __construct($hostname, $username, $password, $database) {
$this->connection = new mysqli($hostname, $username, $password, $database);
if ($this->connection->connect_error) {
// @todo your error handling (use $this->connection->connect_errno, $this->connection->connect_error)
// note that the error may contain user data, so ESCAPE it if you print it.
}
$this->transactionStarted = false;
}
public function escape($input) {
return $this->connection->real_escape_string($input);
}
// Returns type mysqli_result or false.
// @todo add properties? For stuff like CACHE, FOR UPDATE, SQL_CALC_FOUND_ROWS with SELECT FOUND_ROWS();.
public function query($query) {
$this->connection->real_query($query);
if ($this->connection->error) {
// @todo your error handling
// note that the error may contain user data, so ESCAPE it if you print it.
} else {
return new DatabaseResultMySQLi($this->connection);
}
}
public function startTransaction() {
if ($this->transactionStarted) {
// @todo throw exception instead?
die('[error] transaction already running');
} else {
$this->connection->autocommit(false);
$this->transactionStarted = true;
// From user comments:
// To prevent database from hanging after a script crashes during a transaction.
// Is this still an actual problem?
register_shutdown_function(array($this, 'shutdownCheck'));
}
}
public function shutdownCheck() {
if ($this->transactionStarted) {
$this->rollbackTransaction();
}
}
public function commitTransaction() {
// The next line both commits queries in queue (the transaction) and turns autocommit back on.
$this->connection->autocommit(true);
$this->transactionStarted = false;
}
// Should only be called when $this->transactionStarted is true.
// @todo if so, should I include that check in here to really enforce this?
public function rollbackTransaction() {
$this->connection->rollback();
$this->transactionStarted = false;
// Afterwards, turn back on autocommitting.
// It is up to the user to decide whether (s)he wants to continue after a rollback though...
$this->connection->autocommit(true);
}
public function insertId() {
return $this->connection->insert_id;
}
}
class DatabaseResultMySQLi extends MySQLi_Result
{
// Returns an associative array.
public function fetchRow() {
return $this->fetch_assoc();
}
// Returns a single value, for COUNT queries and such.
public function fetchValue() {
$row = $this->fetch_row();
return $row[0];
}
public function numRows() {
return $this->num_rows;
}
public function dataSeek($offset) {
return $this->data_seek($offset);
}
public function freeResult() {
$this->free();
}
}
// example
$db = new DatabaseMySQLi('localhost', 'test', 'test', 'test');
$like = 'dummy';
$res = $db->query("SELECT * FROM example WHERE name LIKE '%".$db->escape($like)."%'");
while ($row = $res->fetchRow()) {
echo '<pre>'.print_r($row, true).'</pre>'; // unescaped output!
}
$res->freeResult();
?>
EDIT2: En als je het dus helemaal netjes wilt doen definieer je abstracte classes / interfaces waar je deze classes van afleid. Onafhankelijk van de SQL API die je dan aan het implementeren bent zou deze moeten voldoen aan deze abstracte classes/interfaces. Hiermee garandeer je weer iets beter dat onafhankelijk van welke implementatie je gebruikt, dat je deze op dezelfde manier aanspreekt (de methodes en parameters zijn altijd hetzelfde). Dat staat dan al iets dichter bij een echte database abstractie laag. Maar dat is nog steeds niet genoeg wanneer je overstapt naar een echte andere database (MySQL -> iets anders). Dan moet je namelijk ook rekening houden met de exacte syntax van je queries. Maar dat soort overstappen zul je niet bepaald snel (of vaak) maken lijkt mij zo...
Ik kom er nog niet uit, ik heb net zoals in jou script mijn return functie naar een nieuwe class verplaatst, zoals dat ook in jou script is, ik heb gezorgd dat er na de query een nieuwe class aangeroepen wordt,
Mij lijkt dat mijn class ongeveer hetzelfde is met doorsturen van waardes, objecten etc, toch zal ik iets belangrijks over het hoofd zien helaas
Fatal error: Call to undefined method db::getRows()
Fatal error: Call to undefined method db::getRows()
Terwijl jij in jou script dezelfde functie aanroept, en verder geen melding maakt in de eerste class of deze aanroept, jij roept in de functie query() dan de nieuwe extended class aan maar daarin wordt niet aangegeven dat de functie fetchrow() uitgevoerd moet worden?
<?php
// mysqli
//$this-> = NULL;
/*
*/
class db {
public function __construct($user,$password,$name,$host='localhost') {
$this->connection = new mysqli($host, $user, $password, $name);
if ($this->connection->connect_errno) {
return $this->connection->connect_error;
}
}
public function _setQuery($sql) {
$this->sql = $sql;
}
public function _getQuery() {
return $this->sql;
}
public function _query($query) {
$this->connection->query($query);
return new dbResult($this->connection);
}
}
class dbResult extends MySQLi_Result{
public function getRows() {
return $this->connection->fetch_assoc();
}
public function getRow() {
//return $this->connection->fetch_assoc($this->connection->query($this->query));
}
}
?>
<?php
// test aanroep:
$db = new db('usr','pwd','mbc13_test');
$db->_query('SELECT * FROM users');
while ($record = $db->getRows())
{
print_r($record);
}
?>
Thomas - 17/01/2014 20:12 (laatste wijziging 17/01/2014 20:21)
Moderator
Kijk nog eens goed naar de while-lus in mijn code, hierin staat while ($row = $res->fetchRow()). $res is je resultset-object, die je terugkrijgt van $db->query() (die poept een nieuw database resultset object uit met die return new DatabaseResultMySQLi()-aanroep). Dat resultset-object wijst naar je database waar het resultaat klaar staat zeg maar, die haal je dan rij voor rij op uit je database in je while-lus. Je database-object ($db) heeft geen methode getRow(s), vandaar die foutmelding. Ik zou ook niet het meervoud gebruiken, want je haalt elke keer maar één rij op.
Dus met $db->query() voer je een query uit op de database, die retourneert een resultset-object waarmee je het resultaat van de query rij voor rij kunt ophalen.
EDIT: of wellicht simpeler: je gebruikt $db (een object van de DatabaseMySQLi klasse) voor o.a. het opbouwen van de connectie en het uitvoeren van queries, en $res (een object van de DatabaseResultMySQLi klasse) voor het ophalen van resultaten.
EDIT2: Jouw DbResult klasse zal ook geen $this->connection kunnen gebruiken in aanroepen van getRow(s) denk ik, omdat MySQLi_Result (de klasse waar wij allebei van extenden) zo niet werkt.
Zulke database wrappers moet je eigenlijk niet meer willen maken als je geen speciale reden heeft. PHP heeft hiervoor een eigen "abstraction layer", http://nl3.php.net/PDO, of je kan iets gebruik zoals Doctrine.
Waarom niet? Je ontkoppelt het op deze manier van een specifieke API-implementatie en een echte database abstractie (laag) bereik je niet.
Zelfs PDO doet dit niet!
Citaat:
PDO provides a data-access abstraction layer, which means that, regardless of which database you're using, you use the same functions to issue queries and fetch data. PDO does not provide a database abstraction; it doesn't rewrite SQL or emulate missing features. You should use a full-blown abstraction layer if you need that facility.
.
Okay, bovenstaande classes zullen niet leiden tot een database abstractie-laag, wellicht had ik het ook een "data-access abstraction layer" moeten noemen in plaats van een "database abstraction (layer)".
Uiteindelijk is het (in dit geval) op het laagste niveau allemaal MySQL. Je wilt waarschijnlijk de flexibiliteit van een echte "database abstraction" ook niet, volgens mij wordt dan het schrijven van queries een regelrechte hel, daarnaast kun je dan niet meer goed gebruik maken van specifieke eigenschappen van bepaalde databases (emulatie? klinkt minder efficient dan "native support"). Hoeveel producten ken jij waar op geregelde momenten de database van type A (on-the-fly?) verwisseld wordt voor die van type B (for no apparent reason)? IMO offer je enorm veel gebruikersgemak op voor flexibiliteit die je niet of nauwelijks gebruikt.
Ik heb deze paradox nooit begrepen, waarom zou je streven naar iets wat je werk (alleen maar!) moeilijker maakt? Is het om je opties open te houden?
Het is misschien wel smaak afhankelijk of je mysqli of PDO fijner vindt. Maar om op jouw argument te komen of je in een applicatie van database wisselt? Het kan, maar het gebeurt niet vaak, dat ben ik met je eens. Maar wat wel regelmatig voorkomt is dat je later in een ander project wel met een andere database komt te werken. Dan ken je de abstractielaag al en hoef je niet een nieuwe te leren.
Daarnaast biedt PDO een boel extra features aan en kan je er makkelijk mee werken in een OOP omgeving. Wanneer je een klasse om de mysqli heen gaat schrijven bouw je het min of meer na, en dat is vaak niet verstandig omdat bestaande paketten goed getest en ontworpen zijn.
Voor grotere projecten is het vaak verstandig om een ORM zoals doctrine te gebruiken maar voor simpelere applicaties zou ik echt voor PDO gaan.
Op internet is er veel te vinden over de pro's en cons van beide lagen, oa deze thread op Stack Overflow: http://stackove...s-and-cons
@fangorN, heb het nu werkend, zag inderdaad de $res over het hoofd omdat ik over het hoofd zag dat de functie elke keer werd uitgevoerd, weer wat nieuws geleerd .
ik heb nu getRow en getValue namen voor de result methods, dit waren mijn getRows en getRow, zoals je opmerkte waren dit geen correcte namen
@joost: redenen om dit te maken:
- Al doende leert men --> liep tijdens het maken tijdens bovenstaand probleem aan en had ik nooit geweten over een resultaat set en pointer en hoe er mee om te gaan.
- het gebruik van deze class is voor als ik in de toekomst bepaalde dingen wil optimaliseren, fout afhandeling, logging van bepaalde dingen, ik dit maar 1x hoef aan te passen
- ik wil tegelijkertijd ook meer leren over classes / methods en vooral schrijven in OOP.
- belangrijkste is dan ook de leerpunten die hier boven staan
class database {
public function __construct($user,$password,$name,$host='localhost') {
$this->connection = new mysqli($host, $user, $password, $name);
if ($this->connection->connect_errno) {
return $this->connection->connect_error;
}
}
function makeDatabaseReady($txt) {
$this->txt = $this->connection->real_escape_string($txt);
$this->txt = trim($this->txt);
return $this->txt;
}
public function _setQuery($sql) {
$this->sql = $sql;
}
public function _getQuery() {
return $this->sql;
}
public function query($query) {
$this->_setQuery($query);
$this->connection->real_query($this->sql);
if ($this->connection->error) {
echo $this->connection->error;
} else {
return new dbResult($this->connection);
}
}
public function dbclose() {
$this->connection->close();
}
}
class dbResult extends MySQLi_Result {
public function getRow() {
return $this->fetch_assoc();
}
public function getValue() {
$row = $this->fetch_row();
return $row[0];
}
public function numRows() {
return $this->num_rows;
}
}