I'm using the following function from CS50 2014 and it's working when I SELECT but it's not working when I try to UPDATE. Here's the query and the function:
query(
"UPDATE users
SET pwtxt = ?, pass = ?, dt = ?, reset = ?
WHERE usr = ?",
NULL,
"compass",
NULL,
0,
1
);
function query(/* $sql [, ... ] */)
{
// SQL statement
$sql = func_get_arg(0);
// parameters, if any
$parameters = array_slice(func_get_args(), 1);
// try to connect to database
static $handle;
if (!isset($handle))
{
try
{
// connect to database
$handle = new PDO("mysql:dbname=" . DATABASE . ";host=" . SERVER, USERNAME, PASSWORD);
// ensure that PDO::prepare returns false when passed invalid SQL
$handle->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
}
catch (Exception $e)
{
// trigger (big, orange) error
trigger_error($e->getMessage(), E_USER_ERROR);
exit;
}
}
// prepare SQL statement
$statement = $handle->prepare($sql);
if ($statement === false)
{
// trigger (big, orange) error
// trigger_error($handle->errorInfo()[2], E_USER_ERROR); need to fix array issue
exit;
}
// execute SQL statement
$results = $statement->execute($parameters);
// return result set's rows, if any
if ($results !== false)
{
return $statement->fetchAll(PDO::FETCH_ASSOC);
}
else
{
return false;
}
}
it appears you can no longer update with NULLS
When your are Updating rows you can't use the fetch methods. That's why it's working only with Select queries.
With PDO, how can I make sure that an UPDATE statement was successful?
You have to use the rowCount method to see how many have been affected by your query. By the way I don't think it's a good idea to try to mix up different queries types in only one method. Return values for a select, an update, an insert or a delete are not the same. You can't sum up these in one same function.
If you want to centralize your database connection, I invite you to create a child class of PDO where you can hard code your DB credentials. Or you can create a global PDO object or better, use a singleton. That way, you will be able to use your PDO instance anywhere without duplicating your DB credentials all around and using PDO native methods for your preparations.
Related
I have a custom query() function in my functions. file. I created a login.php file, but when I make a SQL query, the query() function returns a PDO object, instead of an associative array I desire. I need help to pass parameters to be bound to a stored procedure/prepared statement.
The following is the login.php file:
<?php
// configuration
require("../../includes/config.php");
// if form was submitted
if ($_SERVER["REQUEST_METHOD"] == "POST")
{
// validate submission
if (empty($_POST["username"]))
{
adminapologize("You must provide your username.");
}
else if (empty($_POST["password"]))
{
adminapologize("You must provide your password.");
}
$username = $_POST["username"];
// query database for user
$sql = "SELECT * FROM admin WHERE username = '$username'";
$result = query($sql,array($username));
//var_dump($result);
//exit;
if($sql != false)
{
if($result->rowCount() == 0)
{
printf("No admin yet.");
}
// if we found user, check password
if($result->rowCount() == 1)
{
// first (and only) row
$row = $result->fetch();
// compare hash of user's input against hash that's in database
if ($_POST["username"] == $row["username"] && crypt($_POST["password"], $row["hash"]) == $row["hash"])
{
// remember that user is now logged in by storing user's ID in session
$_SESSION["admin_id"] = $row["admin_id"];
// redirect to admin home
redirect("index.php");
}
}
}
else
{
// else apologize
adminapologize("Invalid username and/or password.");
}
}
else
{
// else render form
adminrender("login_form.php", ["title" => "Admin Log In"]);
}
?>
Be advised that the config.php includes the functions.php file. And the following is the portion of the functions.php file:
/**
* Executes SQL statement, possibly with parameters, returning
* a pdo statement object on success, handling and halting execution on error.
*/
function query($sql, $parameters = null)
{
static $pdo; // define the var as static so that it will persist between function calls
try
{
// if no db connection, make one
if (!isset($pdo))
{
// connect to database
// you should set the character encoding for the connection
$pdo = new PDO("mysql:dbname=" . DB_NAME . ";host=" . DB_SERVER, DB_USERNAME, DB_PASSWORD);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // set the error mode to exceptions
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES,false); // turn emulated prepares off
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE,PDO::FETCH_ASSOC); // set default fetch mode to assoc so that you don't have to explicitly list the fetch mode every place
}
if(empty($parameters))
{
// no bound inputs
$stmt = $pdo->query($sql);
} else {
// has bound inputs
$stmt = $pdo->prepare($sql);
// you should use explicit bindValue() statements to bind any inputs, instead of supplying them as a parameter to the ->execute() method. the comments posted in your thread lists the reasons why.
$stmt->execute($parameters);
}
}
catch (Exception $e)
{
// all errors with the connection, query, prepare, and execute will be handled here
// you should also use the line, file, and backtrace information to produce a detailed error message
// if the error is due to a query, you should also include the $sql statement as part of the error message
// if $pdo ($handle in your code) is set, it means that the connection was successful and the error is due to a query. you can use this to include the $sql in the error message.
trigger_error($e->getMessage(), E_USER_ERROR);
//exit; // note: E_USER_ERROR causes an exit, so you don't need an exit; here.
}
return $stmt; // if the query ran without any errors, return the pdo statement object to the calling code
}
Your help would be much appreciated.
You've got an excellent function, no need to spoil it.
the query() function returns a PDO object, instead of an associative array I desire.
It's actually an object that you want to be returned. As for the array, you can simply get it by chaining fetch to the call:
$result = query($sql,array($username))->fetch(); // voila!
Look, with function returning an object you can get not only single row but dozens different kinds of results. Like single column value with fetchColumn() or many formats supported by fetchAll(). Not to mention you can get numRows() from the object while from array you can't.
With this function in its current form you can run DML queries as well, while returning fetch it will end up with error. Returning an object is a really really cool thing!
The only bad thing about your function is that you are catching an exception and converting it into an error manually, while PHP is already doing it for you.
Just get rid of this try catch block and you will have exactly the same (actually, even better) error reporting.
// all errors with the connection, query, prepare, and execute will be handled here
// you should also use the line, file, and backtrace information to produce a detailed error message
// if the error is due to a query, you should also include the $sql statement as part of the error message
// if $pdo ($handle in your code) is set, it means that the connection was successful and the error is due to a query. you can use this to include the $sql in the error message.
ALL these things PHP is already doing for you, if you simply don't catch an exception.
(save for storing an $sql variable which is not needed actually, as you can find a query following a back trace)
As of the code, it should be five times shorter:
$sql = "SELECT * FROM admin WHERE username = ?";
$row = query($sql,array($username))->fetch();
if($row && crypt($_POST["password"], $row["hash"]) == $row["hash"])
{
// remember that user is now logged in by storing user's ID in session
$_SESSION["admin_id"] = $row["admin_id"];
// redirect to admin home
redirect("index.php");
//this is essential as otherwise anyone will be able to proceed with this page
exit;
}
BTW, I just noticed that you are using your function wrong, sending $username right into the query. I fixed it too.
Edit according to #Your Common Sense's answer who's absolutley right:
Call your function 'query' and do directly on that result a fetch.
For example as mentioned in the comments below:
$rows = query($sql, $params)->fetch();
If you want rows as associative array do
$rows = query($sql, $params)->fetch(PDO::FETCH_ASSOC);
This explicitly returns an associative array.
You have to fetch the result after executing.
$stmt->execute($parameters);
return $stmt->fetch(PDO::FETCH_ASSOC);
This should return an associative array.
See also: http://php.net/manual/de/pdostatement.fetch.php
/**
* Executes SQL statement, possibly with parameters, returning
* a pdo statement object on success, handling and halting execution on error.
*/
function query($sql, $parameters = null)
{
static $pdo; // define the var as static so that it will persist between function calls
$return = false;
try {
// if no db connection, make one
if (!isset($pdo)) {
// connect to database
// you should set the character encoding for the connection
$pdo = new PDO("mysql:dbname=" . DB_NAME . ";host=" . DB_SERVER, DB_USERNAME, DB_PASSWORD);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // set the error mode to exceptions
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES,false); // turn emulated prepares off
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE,PDO::FETCH_ASSOC); // set default fetch mode to assoc so that you don't have to explicitly list the fetch mode every place
}
if(empty($parameters)){
// no bound inputs
$stmt = $pdo->query($sql);
} else {
// has bound inputs
$stmt = $pdo->prepare($sql);
// you should use explicit bindValue() statements to bind any inputs, instead of supplying them as a parameter to the ->execute() method. the comments posted in your thread lists the reasons why.
$stmt->execute($parameters);
$return = $stmt->fetch(PDO::FETCH_ASSOC);
}
} catch (Exception $e)
{
// all errors with the connection, query, prepare, and execute will be handled here
// you should also use the line, file, and backtrace information to produce a detailed error message
// if the error is due to a query, you should also include the $sql statement as part of the error message
// if $pdo ($handle in your code) is set, it means that the connection was successful and the error is due to a query. you can use this to include the $sql in the error message.
trigger_error($e->getMessage(), E_USER_ERROR);
//exit; // note: E_USER_ERROR causes an exit, so you don't need an exit; here.
}
return $return; // if the query ran without any errors, return the pdo statement object to the calling code
I'm making a small CMS for some fun and practice. And I've come across this problem where I have to access a database multiple times in different functions. And the way I do it now by making a new prepared statement with the code and all to access the database in the function doesn't seem very convenient since the code is very repetitive and I'm using mostly the same code for each function. So how would I go about creating a class maybe or some functions that reduce the amount of code used in the functions that gather the information from that database? I currently use the following queries in SQL
SELECT
UPDATE
INSERT
DELETE
So mostly the basic ones. The code I'm using is basic PHP code where I'm using prepared statements to access my database like this:
// Create database connection
$con = db_connect();
// Initialize $error variable for errors
$error = "";
if ($stmt = $con->prepare("SELECT * FROM profiles WHERE username = ?")) {
// Bind the $username variable to the parameter in the query
$stmt->bind_param('s', $username);
// Execute the prepared query
$stmt->execute();
$stmt->store_result();
// Assign the data recieved from the database (if any)
$stmt->bind_result($data);
$stmt->fetch();
if ($stmt->num_rows == 1) {
if (!empty($stmt->error)) {
printf("Error: %s.\n", $stmt->error);
return false;
}
// Query successful
} else {
$error .= "User doesn't exist";
return false;
}
} else {
$error .= 'Could not connect to database';
return false;
}
To me this seems like pretty easy to use code, but when you have to paste it again and again in different functions, then it gets a bit frustrating.
You should use Dependency Injection.
By injecting the Database connection into a Profile's class, you have much more maneuverability to do what you please.
You can change that database to whatever you want (MongoDB, Cassandra, MySQL).
You are only declaring the connection once; which performs better and faster
Makes it easier to test and develop (echo & print_r & unit testing)
Handel exceptions in 1 place
Database is loosely couple with rest of code.
ex:
class Profile {
private $db = null;
public function __construct($db Database) {
$this->db = $db;
}
public function getProfile() {
//ish....
$this->db->query("SELECT * FROM profiles WHERE username = ?");
}
public function insert() {
...
}
public function update() {
...
}
public function delete() {
...
}
}
To access the database, I would do something like this and also implement what you have (prepared statements are great!):
class Database {
private $conn = null;
public function __construct($db Database) {
$this->conn = new PDO('mysql:host=localhost;dbname=myDatabase', $username, $password);
$this->conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
return $this->conn;
}
public function query($sql) {
try {
return $conn->query($sql);
} catch (Exception $e) {
echo $e->getMessage();
}
}
}
A very good explaination and tutorial can be found here: http://code.tutsplus.com/tutorials/dependency-injection-huh--net-26903
In my program I want update some information, so I used:
return ($result->rowCount() == 1)? true: false;
this way, if you save information without any change, false is returned and this is not the result usually we expect.
I change my function to this one
try{
$result=$db->prepare($sql);
$result->execute($arr);
return true;
}catch(Exception $e){
return false;
}
Is it the best way? Does this way guarantee that the update statement worked or not?
Assuming your $db is instance of PDO
When you run PDO::prepare you can detect sql syntax error and when you run PDO::execute you will be sure that syntax is correct. Then application is resposible for knowing if update should have updated something or not.
So here is some sample code:
function update($db, $sql)
{
$preparedSql = $db->prepare($sql);
if(!$preparedSql) {
// syntax error occured
$errorInfo = $db->errorInfo();
// handle error
return false;
}
$db->execute($preparedSql);
// note that rowCount returns 0 when none updated
return $db->rowCount();
}
In your application
$result = update($db, $sql);
if($result === false)
{
// error occured
} elseif($result === 0) {
// zero rows updated
} else {
// some rows were updated
}
Note that it's not tested and for learning purposes only, update function should accept to bind parameters for real world application
The following code updates the table correctly, but it also returns an Exception. Any idea what might be happening here?
public function updateThis($aaa){
try
{
$success = false;
$query = "
UPDATE this_table
SET thing = '0'
WHERE aaa = :aaa";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':aaa', $aaa);
$stmt->execute();
if($this->conn->commit())
$success = true;
return $success;
}
catch(Exception $e)
{
return $e;
}
}
When you are using PDO, Auto-Commit is on by default, unless you specifically turn it off using Begin Transaction. I can't see it in your connection, so are you perhaps trying to commit a transaction that has already been auto-commited?
I am trying to capture database (MYSQL) errors in my PHP web application. Currently, I see that there are functions like mysqli_error(), mysqli_errno() for capturing the last occurred error. However, this still requires me to check for error occurrence using repeated if/else statements in my php code. You may check my code below to see what I mean. Is there a better approach to doing this? (or) Should I write my own code to raise exceptions and catch them in one single place? Also, does PDO raise exceptions? Thanks.
function db_userexists($name, $pwd, &$dbErr)
{
$bUserExists = false;
$uid = 0;
$dbErr = '';
$db = new mysqli(SERVER, USER, PASSWORD, DB);
if (!mysqli_connect_errno())
{
$query = "select uid from user where uname = ? and pwd = ?";
$stmt = $db->prepare($query);
if ($stmt)
{
if ($stmt->bind_param("ss", $name, $pwd))
{
if ($stmt->bind_result($uid))
{
if ($stmt->execute())
{
if ($stmt->fetch())
{
if ($uid)
$bUserExists = true;
}
}
}
}
if (!$bUserExists)
$dbErr = $db->error();
$stmt->close();
}
if (!$bUserExists)
$dbErr = $db->error();
$db->close();
}
else
{
$dbErr = mysqli_connect_error();
}
return $bUserExists;
}
I have created my own code to execute MySQL statements and raise exceptions if they fail, including specific exceptions for different causes of failure. One of the most helpful of these is when collisions occur, allowing me to use try..catch blocks and catch DatabaseCollisionExceptions, and handle those differently from other database exceptions.
What I found easiest for doing this was an MVC design pattern where every table was represented by a PHP class (models) which I could just assign member variables to and call a save method to save to the database, similar to:
try
{
$user = new User();
$user->username = 'bob';
$user->setPassword($_POST['password'); // Could do some SHA1 functions or whatever
$user->save
}
catch(DatabaseCollisionException $ex)
{
displayMessage("Sorry, that username is in use");
}
catch(DatabaseException $ex)
{
displayMessage("Sorry, a database error occured: ".$ex->message());
}
catch(Exception $ex)
{
displayMessage("Sorry, an error occured: ".$ex->message());
}
For more information on similar design patterns, see:
http://www.phpactiverecord.org/
http://book.cakephp.org/view/17/Model-Extensions-Behaviors
http://www.derivante.com/2009/05/14/php-activerecord-with-php-53/
Of course this isn't the only answer, it's just some ideas you might find helpful.
I think exceptions are the best approach. PDO does throw exceptions you just need to set PDO::ERRORMODE_EXCEPTION when you create the object.
this still requires me to check for
error occurrence using repeated
if/else statements
How's that?
You don't need to check for every possible error.
I'd make just final check for the returned value and nothing else.
For what purpose do you use these nested conditions?
You may rewrite it in the following manner:
$bUserExists = false;
$uid = false;
if (!mysqli_connect_errno())
{
if($stmt = $db->prepare("select uid from user where uname = ? and pwd = ?")
->bind_param("ss", $name, $pwd))
{
$stmt->execute();
$stmt->bind_result($uid);
$stmt->fetch();
$stmt->close();
}
if ($uid)
$bUserExists = true;
$db->close();
}
else
{
$dbErr = mysqli_connect_error();
}