I'm trying to execute this function:
<?php
function registerDevice(){
$query = "INSERT INTO devices (device,username) VALUES (:device,:username)";
$query_params = array(
':device' => $_POST['device'],
':username' => $_POST['username'],
);
try {
$stmt = $db->prepare($query);
$result = $stmt->execute($query_params);
}
catch (PDOException $ex) {
$response["success"] = 0;
$response["result"] = "Error154";
die(json_encode($response));
}
}
registerDevice();
?>
The method is works successfully if is not called when is outside the function:
<?php
$query = "INSERT INTO devices (device,username) VALUES (:device,:username)";
$query_params = array(
':device' => $_POST['device'],
':username' => $_POST['username'],
);
try {
$stmt = $db->prepare($query);
$result = $stmt->execute($query_params);
}
catch (PDOException $ex) {
$response["success"] = 0;
$response["result"] = "Error154";
die(json_encode($response));
}
?>
but when i call the function the function does not work at all. I hope you guys can help me out. Thanks
The key is $db variable. You never show how it's initialized: I assume it's created by some external (require) file responsible for creating a connection to DB and storing it into $db var. As file boundaries do not create separate scopes in PHP, that variable is left in the global scope.
It's not a good practice, but it works when the code using $db is also placed in the global scope. But it breaks when the code is moved into a function, which introduces a new - isolated - scope.
(I'd suggest checking this question and its answers, it explains a lot about intricacies of PHP; and believe me, there are some)
One possible way out of this mess is explicitly passing the value of $db variable into registerDevice function as its param. This obviously requires changes in the signature:
function registerDevice($db) {
// ... the rest of the code is the same
}
registerDevice($db);
Note that $_POST variable is a different beast. Actually, there's more of them - $_GET, $_SERVER and so on, those wild things also known as PHP superglobals. You can safely (sort of) use them within any part of your code, whenever it introduces a new scope or not. That's why they're called superglobals, after all.
Still, even with all the power in your disposal it might be a great idea adjusting your function so that it doesn't depend on any magic:
function registerDevice($db, $deviceId, $username) {
// ... the code is the same
}
if (isset($_POST['device'], $_POST['username'])) {
registerDevice($db, $_POST['device'], $_POST['username']);
}
else {
// something is not right with the request
}
The change might seem insignificant, but now your function can take inputs from any source, becoming a step closer to a truly autonomous entity. That, among other things, allows you to 1) test this function in an isolation; 2) reuse this function in other parts of your application.
Related
Usually when it is necessary to communicate with MySQL via PHP I use a template similar to the one below (which is similar to the ones available in beginners tutorials):
// Exception Handler
class customException extends Exception {}
// Database Link (include file in a private directory)
function db_connect()
{
$hostname = "localhost";
$username = "username";
$password = "password";
$database = "database";
$connection = mysqli_connect($hostname, $username, $password, $database);
return $connection;
}
// Template for calling common types of stored procedures:
// select a table row based on the primary key (pk)
function select_pk($connection, string $pk): array
{
// if other database is needed
mysqli_select_db($connection, "database1");
// query execution
$query = sprintf("CALL select__pk('%s')", mysqli_real_escape_string($connection, $pk));
$resource = mysqli_query($connection, $query);
$result = mysqli_fetch_assoc($resource);
// prepare for next query
mysqli_free_result($resource);
while(mysqli_more_results($connection)) mysqli_next_result($connection);
// use exception handling if necessary
if(!isset($result)) throw new customException('pk not found');
return $result;
}
// Typical execution
$connection = db_connect();
try
{
$result = select_pk($connection, $pk);
}
catch(customException $e)
{
/*** do something ***/
}
Although this template is, so far, working fine (single server), I have the impression that:
the preparation for next query is overcomplicated (mysqli_free_result, mysqli_more_results and mysqli_next_result)
it does not deal properly with errors
Question
Any comments or advice on how to improve this template?
Well, first I would give a generalized answer that likely would help other people stumbling upon this question and then review the particular case of yours.
I asked myself exactly the same question a long time ago and eventually came to a set of solutions that ease the database operations using mysqli.
Mysqli connection
I have doubts about storing a connection code in a function. It asks to be misused. A connection to a single database should be established strictly once during a single HTTP request/php instance. but a function's purpose to be called multiple times. It would be better to put the connection code in a file instead, and then just include this file in your code in a single place.
I've got a canonical mysqli connection code that deals with a whole lot of problems before they even appear. So, instead of function db_connect() let's create a file called mysqli.php and put the following code there
<?php
$host = '127.0.0.1';
$db = 'test';
$user = 'root';
$pass = '';
$charset = 'utf8mb4';
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
try {
$conn = new mysqli($host, $user, $pass, $db);
$conn->set_charset($charset);
} catch (\mysqli_sql_exception $e) {
throw new \mysqli_sql_exception($e->getMessage(), $e->getCode());
}
unset($host, $db, $user, $pass, $charset); // we don't need them anymore
Among other solutions it will translate mysql errors into PHP exceptions which is, basically all you need in order to deal with errors.
Running prepared queries
The next problem is rather elaborate code required for the prepared queries in mysqli. To deal with it i wrote a mysqli helper function that eases the process dramatically.
note that although your current approach with mysqli_real_escape_string() is technically safe, it is frowned upon never the less, as it's a subject of human errors of all sorts. Better stick to prepared statements for all queries that involve a PHP variable as input.
So next solution would be a helper function like this
function prepared_query($mysqli, $sql, $params = [], $types = "")
{
if (!$params) {
return $mysqli->query($sql);
}
$types = $types ?: str_repeat("s", count($params));
$stmt = $mysqli->prepare($sql);
$stmt->bind_param($types, ...$params);
$stmt->execute();
return $stmt->get_result();
}
and you will get a tool that will make prepared statements as smooth as regular queries
Calling stored procedures with mysqli
Stored procedures are not easy because of a quirk: every call returns more than one resultset and therefore we need to loop over them. We cannot avoid it but at least we can automatize this process too. We can write a function that encapsulates all the resultset jiggery-pokery.
function prepared_call($mysqli, $sql, $params = [], $types = ""): array
{
$resource = prepared_query($mysqli, $sql, $params, $types);
$data = $resource->fetch_all(MYSQLI_ASSOC);
while(mysqli_more_results($mysqli)) mysqli_next_result($mysqli);
return $data;
}
A specifik function to call for a PK
And finally we can rewrite your select_pk() function
function select_pk($mysqli, string $pk): array
{
$data = prepared_call($mysqli, "CALL select__pk(?)", $pk);
return $data[0] ?? null;
}
I am not really sure we need an exception here though:
include 'mysqli.php';
$result = select_pk($mysqli, $pk);
if (!$result) {
/*** do something ***/
}
This is the function I use to access the DB, theoretically maybe up 10-20 times in a do while loop, though right now I removed the loop so it only can do one db query at a time. Can this be optimized more or is this how transactions and commits are properly done? Also, I don't know what $db->rollBack() actually does, I just saw it on a stackoverflow
<?php
function util_db_access($statement, $array) {
$db = new PDO('mysql:host=localhost;dbname=db;charset=UTF8', 'user', 'pass');
try {
//echo "1";
$db->beginTransaction();
//echo "2";
$stmt = $db->prepare($statement);
//echo "3";
if($stmt->execute($array)) {
$db->commit();
//echo "4";
if($rows = $stmt->fetchAll(PDO::FETCH_ASSOC)) {
//echo "5";
if($stmt->rowCount() < 2) {
$rows = $rows[0];
}
return $rows;
} else {
//echo "6.1";
//$db->commit();
return true;
}
} else {
//echo "6.2";
//$db->commit();
return false;
}
} catch(PDOException $e) {
$db->rollBack();
//log, we are gonna keep a log eventually.. right?
return -1;
}
}
?>
This thing can be optimized very quickly.
For starters you are creating a new connection to the database with every function call. I don't know for sure if the connection is closed when the PDO object goes out of scope but nevertheless it's bad design.
UPDATE
PHP will close the connection to the database when the PDO object is destroyed.
Reference http://php.net/manual/en/pdo.connections.php
Try using a profiler to determine where your bottleneck is - there's one included in xdebug. Given the simplicity of this code, it may be the query itself - try running the query standalone, either using the mysql cli client or MySQL Workbench, and see what timings you get back. If it is the query that's slow, you can use EXPLAIN and the bountiful optimization sections in the MySQL manual to improve it.
$db->rollBack() make a roll back for the transaction. If you don't know what transactions are there is no point for you to use them, because it creates an unnecessary overhead for the server.
commit() permanently writes the data from the query, rollback() undo everything to the state where you called beginTransaction()
Transactions are to be used when its crucial that changes in more then one place happens simultaneously, imagine a transaction of money between two accounts. On simple selects there is absolutely no need for transactions.
(I'm also sceptic to how you use try/catch)
However... If you run one query directly on the db server, is it also slow? Do you open/close the db connection between each call? (if so -(yes)- don't). What is the network relationship between the http/db server?
I can't help rewriting your function, removing useless parts and adding essential ones
function util_db_access($type, $statement, $array) {
static $db;
if (empty($db)) {
$dsn = 'mysql:host=localhost;dbname=db;charset=UTF8';
$opt = array(
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
);
$db = new PDO($dsn, 'user', 'pass', $opt);
}
$stmt = $db->prepare($statement);
$stmt->execute($array);
if($type == 'all') {
return $stmt->fetchAll();
}
if($type == 'row') {
return $stmt->fetch();
}
if($type == 'one') {
return $stmt->fetchColumn();
}
if($type == 'id') {
return $db->lastInsertId();
}
if($type == 'numrows') {
return $stmt->rowCount();
}
}
most likely this new version won't suffer any delays too
now you can use this great function without any delay or inconvenience:
$user = util_db_access("row", "SELECT * FROM users WHERE id = ?", array($someId));
$id = util_db_access("id", "INSERT INTO users SET name=?", array($name));
and so on
However, having just a function for this purpose is quite inconvenient, and I hope you will grow it into class soon. You can take my Safemysql class as a source of some ideas.
I am making a transfer from using mysql query lines to PDO for a current project and i have an issue.
For this task i am not allowed to use any classes (stupid restriction if you ask me)
Basically i was getting a non object error because my main php file could not see the set variable $DBH.
I solved this problem by setting each function with a $DBH global; so it could be used, however ive been told this is bad coding practice. Is this the case? and if so how can i make my function see my config variable.
Config.php
try
{
$DBH = new PDO("mysql:host=host;dbname=db", "username", "Password");
$DBH->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
catch (PDOException $e){
echo $e->getMessage();
}
a php file
function concName($concID)
{
global $DBH; //THIS is the area that im told is bad practice - can this be eliminated?
$stmt = $DBH->prepare("SELECT CONCAT(`Firstname`, ' ', `Surname`) AS 'Membername' FROM `members` WHERE `MemberID`= :MemberID");
$stmt->bindValue(":MemberID",$concID);
$stmt->execute();
while($row = $stmt->fetch())
{
return $row['Membername'];
}
}
Just pass $DBH as a parameter to any function that needs it:
function concName($concID, $DBH)
{
$stmt = $DBH->prepare("SELECT CONCAT(`Firstname`, ' ', `Surname`) AS 'Membername' FROM `members` WHERE `MemberID`= :MemberID");
$stmt->bindValue(":MemberID",$concID);
$stmt->execute();
while($row = $stmt->fetch())
{
return $row['Membername'];
}
}
Rather than the global keyword, you can also access it from the $GLOBALS[] array, which is more explicit about the variable's origins when used in the function. Passing a parameter is still preferable to this though.
function concName($concID)
{
// Better than `global` keyword, use `$GLOBALS['DBH']` every time you access it in outside global scope
// Still not preferred to passing a parameter though.
$stmt = $GLOBALS['DBH']->prepare("SELECT CONCAT(`Firstname`, ' ', `Surname`) AS 'Membername' FROM `members` WHERE `MemberID`= :MemberID");
$stmt->bindValue(":MemberID",$concID);
$stmt->execute();
while($row = $stmt->fetch())
{
return $row['Membername'];
}
}
If you have multiple globals defined in your configuration file, you can wrap them all in an array which you pass into functions needing them. That wraps them tidily into a package of config options made available to any function that needs them.
config.php
// Global array of config options
$config = array();
// various options
$config['option1'] = 'option 1';
$config['option2'] = 12345;
try
{
$config['DBH'] = new PDO("mysql:host=host;dbname=db", "username", "Password");
$config['DBH']->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
catch (PDOException $e){
echo $e->getMessage();
}
Then pass $config to function calls
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();
}
I've been playing around with PDO for the last few days, I'm working on a small CMS system to teach myself OOP skills, but even though it's only a small CMS, I want it to be able to handle whatever the web can throw at it.
This is what I've come up with so far, I'm going to add connection pooling to the constructor to enable large amounts of concurrent connects on demand. I'm very new to this OOP stuff so I'm wanting a little advise and critism, no doubt I've done something terribly wrong here.
I took the top answer to Global or Singleton for database connection? as the base design, although I've added a private constructor as I want to use $this->dbConnectionInstance throughout the class for numerous helper functions to use.
Thanks very much for your time, I really will appreciate any advise you can give me,
-Drew
// Usage Example: $dbconn = dbManager::getConnection();
// $dbconn->query("SELECT * FROM accounts WHERE id=:id", "':id' => $id");
<?php
class dbManager {
private static $dbManagerInstance;
private $dbConnectionInstance;
private $stime;
private $etime;
public $timespent;
public $numqueries;
public $queries = array();
public static function getManager(){
if (!self::$dbManagerInstance){
self::$dbManagerInstance = new dbManager();
}
return self::$dbManagerInstance;
}
// Server details stored in definition file
private function __construct($db_server=DB_SERVER, $db_user=DB_USER, $db_pass=DB_PASS, $db_params=array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8")) {
if(!$this->dbConnectionInstance)
{
try{
$this->dbConnectionInstance = new PDO($db_server, $db_user, $db_pass, $db_params);
$this->dbConnectionInstance->setAttribute(PDO::ATTR_PERSISTENT, PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
$this->dbConnectionInstance = null;
die($e->getMessage());
}
}
return $this->dbConnectionInstance;
}
private function __destruct(){
$this->dbConnectionInstance = null;
}
private function query($sql, $params = array()) {
$this->queries[] = $sql;
$this->numqueries++;
$this->sTime = microtime();
$stmt = $this->dbConnectionInstance->prepare($sql);
$stmt->execute($params);
$this->eTime = microtime();
$this->timespent += round($this->eTime - $this->sTime, 4);
return $stmt;
}
}
?>
Thank you both for your suggestions, I've now added the rollback and commit into my exception handling, I'm just researching the use of buffered queries, I'm not entirely sure what ths will give me?
Looks good, I would add rollback functionality, along with the buffered query/errorInfo suggestions (If you're using a RDBMS that supports transactions):
try {
$this->dbConnectionInstance->beginTransaction();
$stmt = $this->dbConnectionInstance->prepare($sql);
$stmt->execute($params);
$this->dbConnectionInstance->commit();
}catch(PDOException $e){
$this->dbConnectionInstance->rollback();
}
commit() , beginTransaction()
EDIT: added links below for more info on buffered queries:
mysql performance blog
pdo mysql buffered query support
stack overflow: pdo buffered query problem
The code you have dosent look too bad. however if i could make a couple small changes (mainly error handling).
both the prepare and execute statements will return false on error. and you can access the error from $this->dbConnectionInstance->errorInfo() in your example above.
also if you plan on using any large queries I suggest using a buffered query: PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true
looks like a good start. Good luck on your CMS.