It's a little bit more code but I wanted to show the full class. I highlight the points I'd like input after the source.
I've cut comments since they where not in English and translated the important ones.
The class is inspired by PearDb (too old) and Zend_DB (to cluttered / unfinished at the time) and is used in a inhouse application. I know it's not ideal to write your own database handler (I'd go so far to say it's pretty pointless).
<?php
class DbStatement {
private $oStatement;
private $aFieldnames = array();
private $aResultRow = array();
private $aResultSet = array();
private $bMetadata = true;
private $bPreparedFetch = false;
private $iNumRows = false;
private $sQuery;
private $aArgs;
private $fQuerytime;
/**
* @throws DbException
* @param mysqli $oDb Datenbankverbindung
* @param string $sQuery
*/
public function __construct(mysqli $oDb, $sQuery) {
$this->oStatement = $oDb->prepare($sQuery);
$this->sQuery = $sQuery;
if($this->oStatement === false) {
switch($oDb->errno) {
case 1054:
throw new DbNoSuchFieldException($oDb->error, $oDb->errno);
case 1146:
throw new DbNoSuchTableException($oDb->error, $oDb->errno);
default:
throw new DbException(
"Prepared Statement could not be created: ".
$oDb->error." (".$oDb->errno."). Query was: '$sQuery'",
$oDb->errno
);
}
}
}
/**#@+
*
* @param mixed $mParams,...
*/
public function execute() {
$this->_execute(func_get_args());
$this->_done();
}
/**
* @return null|bool|int|string|float
*/
public function getOne() {
$this->_execute(func_get_args());
$this->_fetchRow();
if(isset($this->aResultSet[0][$this->aFieldnames[0]])) {
return $this->aResultSet[0][$this->aFieldnames[0]];
}
return null;
}
/**
* @return array
*/
public function getCol() {
$this->_execute(func_get_args());
$this->_fetchAll();
$sIndex = $this->aFieldnames[0];
$aReturn = array();
foreach($this->aResultSet as $aResultRow) {
$aReturn[] = $aResultRow[$sIndex];
}
return $aReturn;
}
/**
* @return array
*/
public function getRow() {
$this->_execute(func_get_args());
$this->_fetchRow();
if(isset($this->aResultSet[0])) {
return $this->aResultSet[0];
}
return array();
}
/**
* @return array
*/
public function getAssoc() {
$this->_execute(func_get_args());
$this->_fetchAll();
if(isset($this->aFieldnames[0]) && isset($this->aFieldnames[1])) {
$sIndexKey = $this->aFieldnames[0];
$sIndexValue = $this->aFieldnames[1];
$aReturn = array();
foreach($this->aResultSet as $aResultRow) {
$aReturn[$aResultRow[$sIndexKey]] = $aResultRow[$sIndexValue];
}
return $aReturn;
}
return array();
}
/**
* @return array
*/
public function getAll() {
$this->_execute(func_get_args());
$this->_fetchAll();
return $this->aResultSet;
}
/**#@-*/
/**
* @return false|int
*/
public function numRows() {
return $this->iNumRows;
}
/**
* @return int
*/
public function affectedRows() {
return $this->oStatement->affected_rows;
}
/**
* @return int
*/
public function lastInsertId() {
return $this->oStatement->insert_id;
}
public function getLastExecutedQuery() {
$sReturn = $this->sQuery;
if($this->aArgs) {
$sReturn .= "; -- Argumente: ~".implode("~,~", $this->aArgs)."~";
}
return $sReturn;
}
/**
* @throws DbException
*
* @param array $aArgs
*/
private function _execute($aArgs) {
$aArgs = $this->_parseFuncArgs($aArgs);
$this->aArgs = $aArgs;
$iArgs = count($aArgs);
if($iArgs) {
if($this->oStatement->param_count !== $iArgs ) {
throw new DbException(
"Inserting parameters failed: ".$this->oStatement->param_count.
" Parameters expected but ".$iArgs." passed."
);
}
$aRefArgs = array();
foreach(array_keys($aArgs) as $mIndex) {
$aRefArgs[$mIndex] = &$aArgs[$mIndex];
}
array_unshift($aRefArgs, str_repeat("s", $iArgs));
// Needs References
call_user_func_array(array($this->oStatement, "bind_param"), $aRefArgs);
}
$bWorked = $this->oStatement->execute();
if($bWorked === false) {
$sError = sprintf(
"Query failed: %s (%s) Query was: '%s'",
$this->oStatement->error,
$this->oStatement->errno,
$this->sQuery
);
switch($this->oStatement->errno) {
case 1062:
throw new DbKeyViolationException($sError, $this->oStatement->errno);
default:
throw new DbException($sError, $this->oStatement->errno);
}
}
$this->_prepareFetch();
}
private function _prepareFetch() {
if($this->bMetadata && !$this->bPreparedFetch) {
$oMeta = $this->oStatement->result_metadata();
if($oMeta === false) {
$this->bMetadata = false;
} else {
$this->_prepareMetadata($oMeta);
$this->aResultRow = array_fill(0, count($this->aFieldnames), null);
// Ugly but 'bind_result' forces you to pass references
$aRefs = array();
foreach ($this->aResultRow as $iIndex => &$rmValue) {
$aRefs[$iIndex] = &$rmValue;
}
call_user_func_array(array($this->oStatement, "bind_result"), $this->aResultRow);
$this->bPreparedFetch = true;
}
}
}
/**
* @param mysqli_result $oMeta
*/
private function _prepareMetadata(mysqli_result $oMeta) {
$aFields = $oMeta->fetch_fields();
foreach($aFields as $oField) {
$this->aFieldnames[] = $oField->name;
}
}
private function _fetchRow() {
$this->_fetch(true);
}
private function _fetchAll() {
$this->_fetch(false);
}
/*
* @param bool $bOne One line ?
*/
private function _fetch($bOne) {
$this->aResultSet = array();
if($bOne !== true) {
$this->oStatement->store_result();
}
while($this->oStatement->fetch()) {
// Deref
$aTmp = array();
foreach($this->aResultRow as $mValue) {
$aTmp[] = $mValue;
}
$this->aResultSet[] = array_combine($this->aFieldnames, $aTmp);
if($bOne === true) {
break;
}
}
$this->iNumRows = $this->oStatement->num_rows;
$this->_done();
}
private function _done() {
$this->oStatement->free_result();
}
/**
* @param array $aArgs
* @return array
*/
private function _parseFuncArgs($aArgs) {
if(isset($aArgs[0]) && is_array($aArgs[0])) {
return $aArgs[0];
}
return $aArgs;
}
}
The ugly bits (I think, that is why we are here I guess), are in _execute
and _prepareFetch
as the MySQLi API forced us to use references.
The class takes for granted that the passed in MySQLi object is already connected to a database.
If I missed something please let me know.
call_user_func_array
in_execute
and_prepareFetch
into a helper? Hmm, not worth it, good enough as-is. \$\endgroup\$$db->select('id, name')->from(...
\$\endgroup\$