I have my db connection parameters set in a single file which I include on all pages I need it. Connection files looks like so... called connect.php :
$db_host = '111.111.111.111';
$db_database = 'test';
$db_user = 'test';
$db_pass = 'test';
$db_port = '3306';
//db connection
try {
$db = new PDO("mysql:host=$db_host;port=$db_port;dbname=$db_database;charset=utf8", $db_user, $db_pass,
array(
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, //PDO::ERRMODE_SILENT is default setting
PDO::ATTR_PERSISTENT => false //when true
)
);
}
catch(PDOException $e) {
error_log("Failed to connect to database (/connect.php): ".$e->getMessage());
}
When I need to do things with the db I include this file and end up with something like this... called example.php :
require $_SERVER['DOCUMENT_ROOT'].'/assets/functions/connect.php';
$stmt = $db->prepare("
SELECT
accounts.account_id,
FROM accounts
WHERE accounts.account_key = :account_key
");
//bindings
$binding = array(
'account_key' => $_POST['account_key']
);
$stmt->execute($binding);
//result (can only be one or none)
$result = $stmt->fetch(PDO::FETCH_ASSOC);
//if result
if($result)
{
// result found so do something
}
Occasionally the database connection will fail (updating, I shut it down, its being hammered, whatever)... when that happens the PDOException I have in the try/catch works as it should and adds an entry into my error log saying so.
What I would also like to do is add a 'check' in my example.php so it doesn't attempt to do any database work if there is no connection (the include file with my connect script failed to get a connection). How would I go about this and what is the preferred method of doing so?
I'm not sure of the correct way to 'test' $db before my $stmt entry. Would there be a way to retry the connection if it was not set?
I realize I can leave it as it and there would be no problems, other than the database query fails and the code doesn't execute, but I want to have more options like adding another entry to the error log when this happens.
To stop further processings just add an exit() at the end of each catch block, unless you want to apply a finally block.
try {
//...
} catch(PDOException $e) {
// Display a message and log the exception.
exit();
}
Also, throwing exceptions and true/false/null validations must be applied through the whole connect/prepare/fetch/close operations involving data access. You may want to see a post of mine:
Applying PDO prepared statements and exception handling
Your idea with including db connection file I find good, too. But think about using require_once, so that a db connection is created only once, not on any include.
Note: In my example I implemented a solution which - somehow - emulates the fact that all exceptions/errors should be handled only on the entry point of an application. So it's more directed toward the MVC concept, where all user requests are sent through a single file: index.php. In this file should almost all try-catch situations be handled (log and display). Inside other pages exceptions would then be thwrown and rethrown to the higher levels, until they reach the entry point, e.g index.php.
As for reconnecting to db, How it should be correlated with try-catch I don't know yet. But anyway it should imply a max-3-steps-iteration.
Related
Had an issue recently where a major fire at a datacentre destroyed one of our machines. While this server is now offline all of the other servers on our network are still functional, although this has brought to light a problem that I'm hoping to find the answer for.
Is it possible to reduce the amount of time PDO will wait before reporting that the connection failed?
In my particular scenario there are others servers that rely on remote MySql connections to retrieve data from the destroyed server in order to display certain things. Now I have measures in place to handle the situation when the requested data isn't supplied, but have never had to deal with a machine being unreachable until now.
So right now I'm in the situation where my online servers are hanging on page load due to the destroyed server being offline.
Is there any preference or setting that can be provided when trying to make the initial remote connection where you say "return false without throwing a script breaking error after 5 seconds" for example?
I call my connections through a db class :
protected static function getDB()
{
static $db = null;
if ($db === null) {
$dbhost = DB_HOST;
$dbuser = DB_USER;
$dbpass = DB_PASS;
$dbname = DB_NAME;
try {
$db = new PDO("mysql:host=$dbhost;dbname=$dbname;charset=utf8mb4",
$dbuser, $dbpass);
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
echo $e->getMessage();
}
}
return $db;
}
Like this...
$db = static::getDB();
I'm presuming that I can simply return false in the catch exception instead of echoing the error message which answers half of my question if true, but is it possible to speed up the time it will wait before throwing the exception in the event of an unreachable machine?
EDIT
I found this and tried it...
$db->setAttribute(PDO::ATTR_TIMEOUT, '5');
... but it makes no difference to the timeout whatsoever? Does this setting only relate to local connections maybe?
Just found the answer myself...
At first I found this and added it but it made NO difference whatsoever to the time taken before bomb out in my situation, maybe it works in others though?
$db->setAttribute(PDO::ATTR_TIMEOUT, '5');
But the answer that DID work for me was dropping this in right before the database connection :
ini_set("default_socket_timeout", 5);
Hope it helps somebody else.
I am trying to connect to my database with the following code. And it works, but I am not sure how secure is it. Do I must have a private function too? I don't have any examples of how to apply a private function on this code.
$username = 'user';
$dsn = 'mysql:host=localhost; dbname=register';
$password = 'somepassword';
try{
$db = new PDO($dsn, $username, $password);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}catch (PDOException $ex){
echo "Connection failed ".$ex->getMessage();
}
Better use php composer where you can put these details in a environment file .env. It will be secured as .env is hidden and is placed on Server.
Put the connection parameters into a secure place (i.e. not reachable
by HTTP requests, something like the first answer will be nice), don't leave them into PHP script or some file in the same context... if you put there, protect it with htaccess DENY directive
Never echo exceptions into script output, always deal with them (put
into a log file, translate to friendly errors hiding parameters,
etc). The script never should throw exceptions to the user, it must be handled... the user must only see friendly messages from the script, even a "Ops, something bad happen here..." is better than a "ERROR: SQLSTATE[42000] [1049] Unknown database 'users'" (that show the user a part of the database structure, witch is a security problem)
Problem:
I have read quite a number of articles and two books about PDO but I do not seem to find the answer to my question. The question is whether there is a way to include the database connection to PDO as a require_once() and still be able to use prepared statements without a try/catch block?
I currently have a file called settings.php containing the following code.
Code (settings.php):
<?php
// Declaration of database connection information
$settings = [
'host' => '127.0.0.1',
'name' => 'c9',
'port' => '3306',
'charset' => 'utf8',
'username' => 'admin',
'password' => 'root'
];
?>
I put this file outside the document root and include it with require_once() to the actual database connection file.
Code (db.php):
<?php
// Includes database connection information
require_once('../settings.php');
// Connects to a MySQL database
try {
$dbh = new PDO(
sprintf(
'mysql:host=%s;dbname=%s;port=%s;charset=%s',
$settings['host'],
$settings['name'],
$settings['port'],
$settings['charset']
),
$settings['username'],
$settings['password']
);
// Prevents PDO to use emulated prepares and activates error
// mode PDO::ERRMODE_EXCEPTION
$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
// Catches errors raised by PDO
catch (PDOException $e) {
// Prints out errors to text file
file_put_contents('errors.txt', $e->getMessage(), FILE_APPEND);
// Shows generic error message to user
header('Location: 404.php');
exit;
}
?>
Questions:
Since I have the file db.php is it possible to include this in other files for prepared statements? Can prepared statements also be in try/catch block despite that the connection is in one? Appreciate any comments of how to include SELECT / INSERT / UPDATE / DELETE to the aforementioned code.
You've got a lot of unnecessary code there.
1) The try/catch is redundant because PHP will log your errors to the error log by default anyway. Besides, any thing that's going to be helpful will come from the execute() call.
2) Yes you can certainly include your database credentials wherever you want, but keep in mind, require_once() is expensive so make it count. You might consider including the whole block of code on all your other pages instead of just including the credentials and then rewriting the other code.
3) And example of a select statement from your above code would be:
$stmt = $dbh->prepare("SELECT * FROM `table` WHERE `id` = :id");
$stmt->execute(array(":id"=>$id));
$results = $stmt->fetch(PDO::FETCH_ASSOC);
Your question seems to be quite unclear, consists of highly irrelevant parts. Say, there is no connection between prepared statements and a try-catch block, both has nothing to do with include. And there is no particular guide on including SELECT / INSERT / UPDATE / DELETE queries in the code - you just run them wherever you wish.
I can make only some notes, hoping it will clear some of your confusions.
First of all, read my article on PDO. It is still incomplete but it may help you to sort things out.
Now to your questions.
whether there is a way to include the database connection to PDO as a require_once() and still be able to use prepared statements without a try/catch block?
Yes. In most cases you don't need a try catch block at all. However, every time you really need it, you can use a try catch block with no problem.
is it possible to include this in other files for prepared statements?
Yes. That's what include operator is for.
Can prepared statements also be in try/catch block despite that the connection is in one?
Yes. You can have as many such blocks in your code, as you need. However, in most cases you don't need them at all.
how to include SELECT / INSERT / UPDATE / DELETE to the aforementioned code.
There is no point in including these queries there. Just write them after the line where you included your db.php
File : Config.php
<?php
require 'inc.database.php';
// Checking if there already a connection. If not then connect to the database.
if(!$IsConnected){
$Database = new Database();
$Database->connect("localhost", "aih786_raheel", "raheel786", "aih786_basicblog");
$IsConnected = TRUE;
}
?>
I m using my config file on my every page because on every page i need to have my database object. Thing i want to clear is that by this approach can i avoid multiple attemps to connect to the database as it is not a good practice to make same connection again and again.
Lets say i have a login page which is the first page of my cms. The connection will be opened on the login page and now when i move to the dashboard.php page i require the config.php file in this page too...so by this it won't create the connection and object again.
Pleas tell me is this the right approach to achieve my goal and also will it give me the access to the object $Database ? I'm not sure if we can use the object on differnt pages once it has been created on first page.
A very rudimentary approach would be to define a function that returns a database connection on-demand, e.g.:
function getDefaultDatabaseConnection()
{
$db = new Database;
$db->connect(...);
return $db;
}
Usually, I try to fire up one connection per page load that needs it.
If I have the proper variable already stored in SESSION variables, then oftentimes
it is not necessary to fire one up.
Given that, I do consider it proper form to drop the connection object at the end
of the script that called it.
And Jack is right, I use a function to fire up the connection.
function dbConnect_readOnly() {
$host="127.0.0.1";
$user="*********";
$password="********";
$dbname="**********";
try {
$DBH = new PDO("mysql:host=$host;dbname=$dbname", $user, $password);
$DBH->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
}
catch(PDOException $e) {
echo "Unable to connect to database.";
file_put_contents('PDOErrors.txt', $e->getMessage(), FILE_APPEND);
}
return $DBH;
}
and to close:
function dbClose_connection($DBH) {
$DBH = null;
}
Include the script at the top of every page that eeds connectivity just after you check for session variables.
I have a site which uses PHP's PDO library to access a mysql database. The mysql database is highly optimised and has all the suitable indexes to make the queries fast and so on. I am encountering some strange behaviour though in relation to the first query to run for a particular web service.
This particular web-service runs a query against the database and returns a json response which is then fed to a jquery auto-complete.
The query upon first run in a client takes approx 2s to run, after which it drops to hundredths of a second, presumably due to innodb caching.
If I type in an entry in the auto-complete box during a new session then the first query response can take upwards of 5 seconds after which it becomes blisteringly fast to return responses. If I then leave the site for a good period i.e. perhaps an hour(not an exact measure but for the sake of argument, a relatively long period of time) and come back to it the same slow first query behaviour is observed again.
I am using a persistent connection out of necessity and owing to a finite number of connections on the server in connection.
I was wondering if any of you had any ideas which might allow me to mitigate the initial delay a bit more.
$DBH = null;
$host = "127.0.0.1";
$db_name = "my_db";
$user_name = "me";
$pass_word = "something";
try {
# MySQL with PDO_MYSQL
$DBH = new PDO("mysql:host=$host;dbname=$db_name;charset=utf8", $user_name, $pass_word, array(PDO::ATTR_PERSISTENT => true, PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'utf8'"));
$DBH->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
}
catch(PDOException $e) {
error_log( $e->getMessage(), 0 );
}
Updated with answer
Right guys after much testing and after thoroughly checking that it was not a dns issue, I went checking the innodb buffer pool route. Anyway I wrote a stored procedure which uses a query to generate a a query for each table in the database which would thus cause them to be cached in the innodb_buffer_pool. The query to generate the sql queries for each table is from the following SO question. I made only one edit to that query and put in the database() function so that it would work from whichever database it was called from.
I also set it up so that it can be called via PHP without waiting for the script to complete so your normal application continues on.
I hope this helps someone out. As an aside to be even more efficient you cold wrap the exec in a small function to only run it at certain times and so on.
MySQL stored procedure SQL
DELIMITER $$
USE `your_db_name`$$
DROP PROCEDURE IF EXISTS `innodb_buffer_pool_warm_up`$$
CREATE DEFINER=`user_name`#`localhost` PROCEDURE `innodb_buffer_pool_warm_up`()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE sql_query VARCHAR(1000) DEFAULT NULL;
DECLARE sql_cursor CURSOR FOR
SELECT
CONCAT('SELECT `',MIN(c.COLUMN_NAME),'` FROM `',c.TABLE_NAME,'` WHERE `',MIN(c.COLUMN_NAME),'` IS NOT NULL')
FROM
information_schema.COLUMNS AS c
LEFT JOIN (
SELECT DISTINCT
TABLE_SCHEMA,TABLE_NAME,COLUMN_NAME
FROM
information_schema.KEY_COLUMN_USAGE
) AS k
USING
(TABLE_SCHEMA,TABLE_NAME,COLUMN_NAME)
WHERE
c.TABLE_SCHEMA = DATABASE()
AND k.COLUMN_NAME IS NULL
GROUP BY
c.TABLE_NAME;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN sql_cursor;
read_loop: LOOP
FETCH sql_cursor INTO sql_query;
IF done THEN
LEAVE read_loop;
END IF;
SET #stmt_sql = sql_query;
PREPARE stmt FROM #stmt_sql;
EXECUTE stmt;
END LOOP;
CLOSE sql_cursor;
END$$
DELIMITER ;
PHP to call the stored procedure
innodb_warm_up_proc_call.php
<?php
$DBH = null;
$host = "localhost";
$db_name = "your_db_name";
$user_name = "user_name";
$pass_word = "password";
try {
# MySQL with PDO_MYSQL
$DBH = new PDO("mysql:host=$host;dbname=$db_name;charset=utf8", $user_name, $pass_word, array(PDO::ATTR_PERSISTENT => true, PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'utf8'"));
$DBH->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
$sql = "CALL innodb_buffer_pool_warm_up()";
$STH = $DBH->prepare( $sql );
$STH->execute();
}catch( PDOException $e ) {
error_log( $e->getMessage() . ' in ' .$e->getFile(). ' on line ' .$e->getLine(), 0 );
}
?>
PHP to run the above script silently and without waiting for it to complete
innodb_warm_up.php
<?php
$file_to_execute = dirname(__FILE__) . "/innodb_warm_up_proc_call.php";
//Run the stored procedure but don't wait around for a chat
exec("php -f {$file_to_execute} >/dev/null 2>&1 &");
?>
When addressing a particular web service, change it's domain name to IP address.
Most likely it will eliminate such a delay (caused by DNS lookup)
Thanks for all the help and great suggestions on this one. I thoroughly checked the dns and other solutions but in the end it turned out to be the innodb page buffer pool. I have coded up a solution for myself and have added it in its entirety in my question above so hopefully it will be of use.
Thanks again for the help.