I have function in my database class that returns the id of the people that are from the a specific country such as spain. But for some reason I only get one value, but there are many people with the same country. here is the function:
Class DbAb {
private $db;
public function sameCountry($country) {
$query = "SELECT id FROM users WHERE country = ? ";
$stmt = $this->db->prepare($query);
$stmt->bind_param("s", $country);
if ($stmt->execute()) {
$stmt->bind_result($sameCountry);
$stmt->fetch();
return $sameCountry;
}
return false;
}
}
$sameC = new DbAb();
$samePeople = $sameC->samecountry("spain");
print_r($samePeople);
Does anyone know how to return an array of results? I have tried to define the variable as an array but still doesn't work...
The bind_result($var) + fetch() inserts a single row into the $var variable.
If you want to return an array of ids from your method, you need to first create an empty array, then for each row, insert into it.
eg. replace this:
$stmt->bind_result($sameCountry);
$stmt->fetch();
return $sameCountry;
with this:
$arr = array();
$stmt->bind_result($id);
while ( $stmt->fetch() ) {
$arr[] = $id;
}
return $arr;
Related
I Have a basic function that looks like this:
public function query($query, $params = []) {
$statement = $this->db->prepare($query);
// Bind parameters based on value's type
foreach ($params as $key => $value) {
if(is_int($value)) {
$statement->bindParam($key + 1, $value, PDO::PARAM_INT);
} else {
$statement->bindParam($key + 1, $value, PDO::PARAM_STR);
}
}
$statement->execute();
return $statement;
}
For whatever reason, when I run something like this:
public static function photosByTag($tag, $user = null) {
$db = new DBConnection();
$query = "SELECT * FROM photos JOIN tags ON tags.photo = photos.pid WHERE tag LIKE ? AND owner = ?";
$params = [$tag, $user];
$result = $db->query($query, $params);
return $result->fetchAll();
}
photosByTag('city', 1)
It doesn't work. If I replace the AND owner = ? with AND owner = 1 it works fine. Something is wrong when binding integers as params, but I don't know what or why.
The problem isn't the bind, it is the loop. If you look at the manual, the second parameter for bindParam (&$variable) requires a reference. Your loop destroys that reference once it reassigns $value. The solution would be to use $params[$key] instead of $value in the bindParam()
Seems kind of redundant to do it this way when you can just use the execute() statement to bind the parameters.
$statement->execute($params);
Just let PDO handle how it assigns the variables. All your doing is checking what is submitted and then choosing the type, you're not enforcing a type, so it is probably similar to what PDO::execute does as is.
Okay I have a function called sendQuery which sends your query.
I know how to do it with BindParams, but I can't really think of a way to make it work with bind values inside a execute.
This is the code:
public function sendQuery($query, array $value, $do_fetch)
{
$query_process = $this->db->prepare($query);
if(!$query_process->execute($binds))
{
throw new excpetion ("An error has occured!");
}
$this->insert = $this->db->lastInsertId();
$this->row_count = $query_process->rowCount();
if($fetch == true)
{
return $query_process->fetchAll();
}
}
As you see, it executes with $binds,
Works like (WHERE user = ?), but I want to send queries like this:
(WHERE user = :user) instead of a ' ? ', and multiple of them.
How do I do so?
You have to do exactly the same.
Just get rid of useless code and use consistent variable naming
public function sendQuery($query, array $binds, $do_fetch)
{
$stm = $this->db->prepare($query);
$stm->execute($binds);
$this->insert = $this->db->lastInsertId();
$this->row_count = $stm->rowCount();
if($do_fetch)
{
return $stm->fetchAll();
}
}
$sql = "SELECT * FROM t WHERE c1=:name AND c2=:age";
$param = array ("name" => $name,"age" => $age);
$data = $db->sendQuery($sql, $data, 1);
However, instead of just single function I would create a set of them:
query() to run non-select queries
getOne() preforms select and returns scalar value
getRow() returns a row
getAll returns all rows
it could be extremely handy
How can I alter my PDO wrapper class, so that if I expect a single row result with my query it uses fetch() and if it expects multiple results it uses fetchAll().
Right now, if I have only one result I still have to loop through the result array and that seem pretty unpracticable to me.
Query in the model:
public function doccEdit() {
$id = mysql_real_escape_string($_GET['id']);
$this->result = $GLOBALS['db']->select("creditcards", "id = ?", $id);
print_r($this->result);
}
In the wrapper class:
public function run($sql, $bind="") {
$this->sql = trim($sql);
$this->bind = $this->cleanup($bind);
$this->error = "";
try {
$pdostmt = $this->prepare($this->sql);
if($pdostmt->execute($this->bind) !== false) {
if(preg_match("/^(" . implode("|", array("select", "describe", "pragma")) . ") /i", $this->sql))
return $pdostmt->fetchall(PDO::FETCH_OBJ);
elseif(preg_match("/^(" . implode("|", array("delete", "insert", "update")) . ") /i", $this->sql))
return $pdostmt->rowCount();
}
} catch (PDOException $e) {
$this->error = $e->getMessage();
$this->debug();
return false;
}
}
DON'T TRY to automate everything
The less magic in your code, the easier support and less painful troubles.
Don't try to stuff all the logic into one single method. It's a class! You can create as many methods as you need.
When you need rowCount() - select it explicitly! It's not that hard.
But when you stumble upon this code after couple months, you will know what does this value mean.
When you need single row - use a method to get a single row.
When you need many rows - use a method to get many rows.
It is simple and extremely unambiguous!
When you turn back to your code after 2 months, you will have absolutely no idea, what did you expected. So - always write it explicitly.
Here is an excerpt from my mysqli wrapper class to give you an idea:
public function query()
{
return $this->rawQuery($this->prepareQuery(func_get_args()));
}
/**
* Helper function to get scalar value right out of query and optional arguments
*
* Examples:
* $name = $db->getOne("SELECT name FROM table WHERE id=1");
* $name = $db->getOne("SELECT name FROM table WHERE id=?i", $id);
*
* #param string $query - an SQL query with placeholders
* #param mixed $arg,... unlimited number of arguments to match placeholders in the query
* #return string|FALSE either first column of the first row of resultset or FALSE if none found
*/
public function getOne()
{
$query = $this->prepareQuery(func_get_args());
if ($res = $this->rawQuery($query))
{
$row = $this->fetch($res);
if (is_array($row)) {
return reset($row);
}
$this->free($res);
}
return FALSE;
}
/**
* Helper function to get single row right out of query and optional arguments
*
* Examples:
* $data = $db->getRow("SELECT * FROM table WHERE id=1");
* $data = $db->getOne("SELECT * FROM table WHERE id=?i", $id);
*
* #param string $query - an SQL query with placeholders
* #param mixed $arg,... unlimited number of arguments to match placeholders in the query
* #return array|FALSE either associative array contains first row of resultset or FALSE if none found
*/
public function getRow()
{
$query = $this->prepareQuery(func_get_args());
if ($res = $this->rawQuery($query)) {
$ret = $this->fetch($res);
$this->free($res);
return $ret;
}
return FALSE;
}
/**
* Helper function to get single column right out of query and optional arguments
*
* Examples:
* $ids = $db->getCol("SELECT id FROM table WHERE cat=1");
* $ids = $db->getCol("SELECT id FROM tags WHERE tagname = ?s", $tag);
*
* #param string $query - an SQL query with placeholders
* #param mixed $arg,... unlimited number of arguments to match placeholders in the query
* #return array|FALSE either enumerated array of first fields of all rows of resultset or FALSE if none found
*/
public function getCol()
{
$ret = array();
$query = $this->prepareQuery(func_get_args());
if ( $res = $this->rawQuery($query) )
{
while($row = $this->fetch($res))
{
$ret[] = reset($row);
}
$this->free($res);
}
return $ret;
}
/**
* Helper function to get all the rows of resultset right out of query and optional arguments
*
* Examples:
* $data = $db->getAll("SELECT * FROM table");
* $data = $db->getAll("SELECT * FROM table LIMIT ?i,?i", $start, $rows);
*
* #param string $query - an SQL query with placeholders
* #param mixed $arg,... unlimited number of arguments to match placeholders in the query
* #return array enumerated 2d array contains the resultset. Empty if no rows found.
*/
public function getAll()
{
$ret = array();
$query = $this->prepareQuery(func_get_args());
if ( $res = $this->rawQuery($query) )
{
while($row = $this->fetch($res))
{
$ret[] = $row;
}
$this->free($res);
}
return $ret;
}
Look - from the function name you can always tell which result to expect:
$name = $db->getOne('SELECT name FROM table WHERE id = ?i',$_GET['id']);
$data = $db->getAll("SELECT * FROM ?n WHERE mod=?s LIMIT ?i",$table,$mod,$limit);
Don't be fooled by such a pitfall like number of returned rows.
There could be honest one row in the resultset which you intend to populate with fetchAll. So, it will return single-dimensional array instead of multi-dimensional and you will have plenty of video effects on your page
Since you didn't mark an answer as accepted. I thought I'd answer you question. I found this while looking for the answer myself. I agree with "Your Common Sense" in that they should be two separate functions. However, in direct answer to your question, this is what I had (PDO example rather than mysqli):
function select($sql,$params=NULL,$fetchType=NULL){
try{
$qry = $this->db->prepare($sql);
$qry->execute($params);
if($qry->rowCount() > 1){
if($fetchType == 'OBJ'){//returns object
$results = $qry->fetchAll(PDO::FETCH_OBJ);
}elseif($fetchType == 'NUM'){//-numerical array
$results = $qry->fetchAll(PDO::FETCH_NUM);
}else{//default - associative array
$results = $qry->fetchAll(PDO::FETCH_ASSOC);
}
}
else{
if($fetchType == 'OBJ'){//returns object
$results = $qry->fetch(PDO::FETCH_OBJ);
}elseif($fetchType == 'NUM'){//-numerical array
$results = $qry->fetch(PDO::FETCH_NUM);
}else{//default - associative array
$results = $qry->fetch(PDO::FETCH_ASSOC);
}
}
if($results){
return $results;
}else{
return NULL;
}
}
catch(PDOException $err){
$this->logError($err);
}
}
However I found that if I queried all rows in a table but there was only one row in the table it would return a 1-d array instead of a 2-d array. My code to handle the result wouldn't work for both types of arrays. I could handle that each time but I found it just easier to, as stated above, separate them into different functions so if I knew there would be only one answer I could call the appropriate function. This is what I have now:
function select($sql,$params=NULL,$fetchType=NULL){
try{
$qry = $this->db->prepare($sql);
$qry->execute($params);
if($fetchType == 'OBJ'){//returns object
$results = $qry->fetch(PDO::FETCH_OBJ);
}elseif($fetchType == 'NUM'){//-numerical array
$results = $qry->fetch(PDO::FETCH_NUM);
}else{//default - associative array
$results = $qry->fetch(PDO::FETCH_ASSOC);
}
if($results){
return $results;
}else{
return NULL;
}
}
catch(PDOException $err){
$this->logError($err);
}
}
function selectAll($sql,$params=NULL,$fetchType=NULL){
try{
$qry = $this->db->prepare($sql);
$qry->execute($params);
if($fetchType == 'OBJ'){//returns object
$results = $qry->fetchAll(PDO::FETCH_OBJ);
}elseif($fetchType == 'NUM'){//-numerical array
$results = $qry->fetchAll(PDO::FETCH_NUM);
}else{//default - associative array
$results = $qry->fetchAll(PDO::FETCH_ASSOC);
}
if($results){
return $results;
}else{
return NULL;
}
}
catch(PDOException $err){
$this->logError($err);
}
}
While writing a pdo statement, is it possible to repeat the value of a variable? I mean:
$query = "UPDATE users SET firstname = :name WHERE firstname = :name";
$stmt = $dbh -> prepare($query);
$stmt -> execute(array(":name" => "Jackie"));
Please note that I repeat the ":name" nameholder whereas I provide the value only once. How can I make this work?
The simple answer is: You can't. PDO uses an abstraction for prepared statements which has some limitations. Unfortunately this is one, you have to work-around using something like
$query = "UPDATE users SET firstname = :name1 WHERE firstname = :name2";
$stmt = $dbh -> prepare($query);
$stmt -> execute(array(":name1" => "Jackie", ":name2" => "Jackie"));
In certain cases, such as emulated prepared statements with some versions of the PDO/MySQL driver, repeated named parameters are supported; however, this shouldn't be relied upon, as it's brittle (it can make upgrades require more work, for example).
If you want to support multiple appearances of a named parameter, you can always extend PDO and PDOStatement (by classical inheritance or by composition), or just PDOStatement and set your class as the statement class by setting the PDO::ATTR_STATEMENT_CLASS attribute. The extended PDOStatement (or PDO::prepare) could extract the named parameters, look for repeats and automatically generate replacements. It would also record these duplicates. The bind and execute methods, when passed a named parameter, would test whether the parameter is repeated and bind the value to each replacement parameter.
Note: the following example is untested and likely has bugs (some related to statement parsing are noted in code comments).
class PDO_multiNamed extends PDO {
function prepare($stmt) {
$params = array_count_values($this->_extractNamedParams());
# get just named parameters that are repeated
$repeated = array_filter($params, function ($count) { return $count > 1; });
# start suffixes at 0
$suffixes = array_map(function ($x) {return 0;}, $repeated);
/* Replace repeated named parameters. Doesn't properly parse statement,
* so may replacement portions of the string that it shouldn't. Proper
* implementation left as an exercise for the reader.
*
* $param only contains identifier characters, so no need to escape it
*/
$stmt = preg_replace_callback(
'/(?:' . implode('|', array_keys($repeated)) . ')(?=\W)/',
function ($matches) use (&$suffixes) {
return $matches[0] . '_' . $suffixes[$matches[0]]++;
}, $stmt);
$this->prepare($stmt,
array(
PDO::ATTR_STATEMENT_CLASS => array('PDOStatement_multiNamed', array($repeated)))
);
}
protected function _extractNamedParams() {
/* Not actually sufficient to parse named parameters, but it's a start.
* Proper implementation left as an exercise.
*/
preg_match_all('/:\w+/', $stmt, $params);
return $params[0];
}
}
class PDOStatement_multiNamed extends PDOStatement {
protected $_namedRepeats;
function __construct($repeated) {
# PDOStatement::__construct doesn't like to be called.
//parent::__construct();
$this->_namedRepeats = $repeated;
}
/* 0 may not be an appropriate default for $length, but an examination of
* ext/pdo/pdo_stmt.c suggests it should work. Alternatively, leave off the
* last two arguments and rely on PHP's implicit variadic function feature.
*/
function bindParam($param, &$var, $data_type=PDO::PARAM_STR, $length=0, $driver_options=array()) {
return $this->_bind(__FUNCTION__, $param, func_get_args());
}
function bindValue($param, $var, $data_type=PDO::PARAM_STR) {
return $this->_bind(__FUNCTION__, $param, func_get_args());
}
function execute($input_parameters=NULL) {
if ($input_parameters) {
$params = array();
# could be replaced by array_map_concat, if it existed
foreach ($input_parameters as $name => $val) {
if (isset($this->_namedRepeats[$param])) {
for ($i=0; $i < $this->_namedRepeats[$param], ++$i) {
$params["{$name}_{$i}"] = $val;
}
} else {
$params[$name] = $val;
}
}
return parent::execute($params);
} else {
return parent::execute();
}
}
protected function _bind($method, $param, $args) {
if (isset($this->_namedRepeats[$param])) {
$result = TRUE;
for ($i=0; $i < $this->_namedRepeats[$param], ++$i) {
$args[0] = "{$param}_{$i}";
# should this return early if the call fails?
$result &= call_user_func_array("parent::$method", $args);
}
return $result;
} else {
return call_user_func_array("parent::$method", $args);
}
}
}
In my case this error appeared when I switched from dblib freedts to sqlsrv PDO driver. Dblib driver handled duplicate parameters names with no errors. I have quite complicated dynamic queries with lots of unions and a lot of duplicated params so I used following helper as a workaround:
function prepareMsSqlQueryParams($query, $params): array
{
$paramsCount = [];
$newParams = [];
$pattern = '/(:' . implode('|:', array_keys($params)) . ')/';
$query = preg_replace_callback($pattern, function ($matches) use ($params, &$newParams, &$paramsCount) {
$key = ltrim($matches[0], ':');
if (isset($paramsCount[$key])) {
$paramsCount[$key]++;
$newParams[$key . $paramsCount[$key]] = $params[$key];
return $matches[0] . $paramsCount[$key];
} else {
$newParams[$key] = $params[$key];
$paramsCount[$key] = 0;
return $matches[0];
}
}, $query);
return [$query, $newParams];
}
Then you can use it this way:
$query = "UPDATE users SET firstname = :name WHERE firstname = :name";
$params = [":name" => "Jackie"];
// It will return "UPDATE users SET firstname = :name WHERE firstname = :name1"; with appropriate parameters array
list($query, $params) = prepareMsSqlQueryParams($query, $params);
$stmt = $dbh->prepare($query);
$stmt->execute(params);
Ok so I have 2 classes. Photo class and PhotoMapper class.
Photo class contains all the values and outputs them.
PhotoMapper sets the values to the Photo class, by a assign class, like this:
$query = "SELECT * FROM users";
$query = $this->_pdo->prepare($query);
$query->bindValue(":id", $photo->id());
$query->execute();
$data = $query->fetch();
$photo->assign($data);
And the assign:
public function assign($data){
if (is_array($data)) {
foreach ($data as $name => $value)
{
$this->{'_' . $name} = $value;
}
}
}
Now where would i check for if $query->rowCount() > 0 ?
Should i inside is_array after the foreach, make a $this->rowCount = .. ?
What would be best to perform this check? I would like to check for the rowCount outside of both classes..
$photo = new Photo($albumID, $photoID, $view, $userID);
$photoMapper->select($photo, $view);
Is how it looks outside the classes. How can i check and output error if select(which is the query above) didnt find any rows?
I would need to have 2 queries? One to check, and one to select them? or?..
Well, if you're expecting data from the fetch operation but there isn't any (you could simply find out by checking if $data has any contents), you should throw an Exception. The following code snippet would typically be placed before the fetch:
// ...
$query->execute();
if ( !( $data = $query->fetch() ) ) {
throw new Exception('photo could not be loaded');
}
$photo->assign($data);
// ...
However, if you want the code to continue regardless, you can reverse the if condition, and put the assign call inside the statement;
$query->execute();
if ( ( $data = $query->fetch() ) ) {
$photo->assign($data);
}