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
Related
I'm dealing with a PHP application with what seems to have a peculiarity: One of its files (helpers.php) has a couple of functions that includes another file, and the included file (db_connection.php) includes the file that originally included it.
helpers.php:
<?php
function lineBreak()
{
return "\n<br>\n";
}
function saveScoreToDB($score)
{
//session_start(); // Already started
$usuario_id = $_SESSION["usuario_id"];
$etapa = $_SESSION["etapa"];
try
{
$query_etapa = "SELECT id FROM etapas WHERE numero = $etapa";
require_once "db_connection.php";
// `$db_link` works perfectly fine here:
$etapa_id = $db_link->query($query_etapa)->fetchColumn();
$query_score = "INSERT INTO score
(
usuario_id,
etapa_id,
pontos
)
VALUES
(
$usuario_id,
$etapa_id,
$score
)";
$db_link->query($query_score);
}
catch (Exception $e)
{
$_SESSION["error_message"] = $e->getMessage();
header("Location: erro.php");
}
}
function completeTest($redirectTo)
{
unset($_SESSION["etapa"]);
$usuarioId = $_SESSION["usuario_id"];
// TODO: try/catch
try
{
$queryEmailUsuario = "SELECT email FROM usuarios WHERE id = $usuarioId";
$queryNomeUsuario = "SELECT nome FROM usuarios WHERE id = $usuarioId";
require_once "db_connection.php";
// `$db_link` does *not* work here. Why?
$emailUsuario = $db_link->query($queryEmailUsuario)->fetchColumn();
$nomeUsuario = $db_link->query($queryNomeUsuario)->fetchColumn();
// Routine to send email using the variables above
}
catch (Exception $ex)
{
// TODO
}
}
db_connection.php:
<?php
require_once "db_credentials.php";
require_once "helpers.php";
// Variables used here come from `db_credentials.php`
$dsn = "mysql:host=$host;dbname=$dbname;port=3307;charset=utf8;";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false
];
try
{
$db_link = new PDO($dsn, $user, $pass, $options);
}
catch (PDOException $e)
{
echo "Error connecting to the database.";
echo lineBreak();
echo $e->getMessage();
echo lineBreak();
echo lineBreak();
}
Notice how in the first script variable $db_link is used in two different functions, both of which include the file where this variable is defined. Within the first function (saveScoreToDB), the variable is available and the function works fine; but within the second (completeTest) it is not available and I get an undefined variable error.
Why is that? How to make it work?
The first require_once() works because that's the "once", but it's only in-scope in that single function call, so $db_link gets tossed out at the end of the function call and is never seen again. You can change that to require(), but creating a new connection for every single function call is... not going to work out well in the long run.
Ideally you create the connection once and then pass it in via parameters where it is needed, eg:
require_once('db_credentials.php');
saveScoreToDB($score, $db_link);
completeTest($redirectTo, $db_link)
But that might get a bit tedious, right? Well this is where classes become useful.
class MyThing {
protected $db;
public function __construct(\PDO $db) {
$this->db = $db;
}
public function saveScoreToDB($score) {
$this->db->prepare(...);
}
public function completeTest($redirectTo) {
$this->db->prepare(...);
}
}
$thing = new Mything($db_link);
$thing->saveScoreToDB(42);
$thing->completeTest('yes');
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.
I'm attempting to perform a query inside a function but it return me "No database selected". I regularly have re-initialized the PDO object inside it
$db = "mysql:host=localhost;dbname=my_database";
$pdo = new PDO($db, 'dbname', 'dbpassword');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
//some operations.. Works fine!
function sendMail($to)
{
$db = "mysql:host=localhost;dbname=my_database";
$pdo = new PDO($db, 'dbname', 'dbpassword');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$qryString="SELECT Codice FROM users WHERE Mail=:mail";
$qry = $pdo->prepare($qryString);
$params = array("mail" => $to);
$qry->execute($params); //won't work
}
Note that operations on the DB outside the function works fine.
The problem is that the code won't work neither passing the global $pdo object.
This is the actual code
$db = "mysql:host=localhost;dbname=my_database";
$pdo = new PDO($db, 'dbname', 'dbpassword');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
sendMail($mail, $pdo)
function sendMail($to, PDO $pdo)
{
$qryString="SELECT Codice FROM users WHERE Mail=:mail";
$qry = $pdo->prepare($qryString);
$params = array("mail" => $to);
$qry->execute($params);
}
Variable scope has absolutely nothing to do with database connection details.
If you have a connection where database selected, the same database will be selected if you are using this connection inside a function.
So, this is a clear case of too localized question, as the problem is obviously of typo-like - connecting to wrong database, misspelling variable name, database name and such.
Unfortunately, regular PHP user has very little knowledge on performing correct, reproduceable experiment to prove their assumption. Instead, they guess the reason by indirect consequences.
You just have to write the code you told us about and see that database is selected all right. And then turn to search for real reason. Full error reporting (E_ALL) often helps a lot.
Pass the PDO object to the function, because the scope of $pdo is outside the scope of sendMail().
function sendMail(PDO $pdo, $to) {
$queryString = "SELECT Codice FROM users WHERE Mail=:mail";
$statement = $pdo->prepare($queryString);
$params = array("mail" => $to);
$result = $statement->execute($params);
}
or
function sendMail($to) {
global $pdo;
$queryString = "SELECT Codice FROM users WHERE Mail=:mail";
$statement = $pdo->prepare($queryString);
$params = array("mail" => $to);
$result = $statement->execute($params);
}
Feeling a little stupid to ask such a question, but this code block is driving me crazy.
function __construct() {
$db = new db();
$this->db = $db->pdo;
}
function getEmployeeDetails() {
$eid = $this->db->quote($this->eid);
try {
$sql = $this->db->query("
SELECT email, cnumber
FROM employees
WHERE EID = $eid
");
$r = $sql->fetch();
$this->email = $r[0];
$this->cnumber = $r[1];
}
catch (PDOException $e) {
throw new Exception("failed");
}
}
It doesn't throw an exception but fails inside the try block - "Call to a member function fetch() on a non-object".
var_dump of the statement object returns 'false'. Why?
I've tried running the query independently, inside MySql. It returns 1 row.
It's hard to tell whether you have done this, but PDO doesn't throw exceptions by default, except on connection failures. You have to specifically add this:
$this->db = $db->pdo;
$this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
Without this, errors that occur during the query will cause ->query() to return false and that's obviously not an object that will have the ->fetch() method. You can also specify this attribute as part of the constructor call.
Also, you could use prepared statements instead of using ->quote():
$stmt = $this->db->prepare("SELECT email, cnumber
FROM employees
WHERE EID = ?");
$stmt->execute(array($this->eid));
$r = $stmt->fetch();
In anticipation of mysql_query being deprecated PHP 5.5.0, I have been working on a class to handle all my DB queries :
class DataBaseClass {
//.....some other function and variables declared here....
function GetConnection() {
try {
$this->conn = new PDO("mysql:host=" . DB_HOST . ";dbname=" . DB_NAME, DB_USER, DB_PASS);
$this->conn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
}
catch(PDOException $e) {
echo $e->getMessage();
}
return $this->conn;
}
function Query($str_sql, $arr_parameters = array()) {
try {
$this->str_mysql_error = $this->int_num_rows = $this->int_num_affected_rows = $this->int_mysql_insert_id = '';
if (count($arr_parameters) > 0) {
$obj_result = $this->conn->prepare($str_sql);
$obj_result->execute($arr_parameters);
} else {
$obj_result = $this->conn->query($str_sql);
}
}
catch(PDOException $e) {
$this->str_mysql_error = $e->getMessage() . $str_sql;
}
}
}
Then I have another class to create new user:
class AddNewUser {
//.....some other function and variables declared here....
function InsertUser() {
$str_sql = "INSERT INTO (uname, name, email, pass, user_regdate, theme) VALUES )";
$_SESSION['db_connection']->Query($str_sql, '');
}
}
Now on my main user creation page I have :
$_SESSION['db_connection'] = new DataBaseClass;
//Reason I used $_SESSION to store my DB object, is so that it can be accessible everywhere.
//Did not want to use "global" everywhere. Not sure if this is he best way???
$cls_new_user = new AddNewUser ();
$cls_new_user->InsertUser(); //Does not raise PDOExecption although SQL cleary wrong inside this method
if ( $_SESSION['db_connection']->str_mysql_error) {
//show error in error div
}
$str_sql = "SELECT some wrong SQL statment";
$_SESSION['db_connection']->Query($str_sql); // This does raise PDOExecption
if ( $_SESSION['db_connection']->str_mysql_error) {
//show error in error div
}
I'm not sure why the DB class function "Query" would not raise an exception on clearly wrong SQL when called from another class. But same function called from main page code (not inside function / class) raises and exception error.
Also, the "InsertUser" function does not execute / insert anything into DB even if SQL correct.
Could it be scope related, or the fact that I'm trying to enforce global scope of my DB object by putting it in $_SESSION ??
Am I going about this the wrong way? Reason for going class route to encapsulate all my DB calls was to avoid any deprecation issues in future - only having to update class.
Make your function this way.
function Query($str_sql, $arr_parameters = array()) {
$stmt = $this->conn->prepare($str_sql);
$stmt->execute($arr_parameters);
}
I am pretty sure that exception would be thrown
The only issue can be with catching exceptions, not throwing. And it could be caused by Namespace, not scope. To be certain, you can always prepend all PDO calls with a slash:
\PDO::FETCH_ASSOC
\PDOException
etc.