I have inherited some PHP code that is not ideal and I'm not sure what the most efficient way of rectifying it is.
Basically all DB calls are made through a custom function like this:
function dbcommand($req)
{
global $mysqli;
$backtrace = debug_backtrace();
$ret = array();
$res = mysqli_query($mysqli, $req) or die('SQL Query Error: '. $mysqli->error .', query ['. $req .'] at '. $backtrace[0]['file'] .':'. $backtrace[0]['line']);
if (strpos(strtoupper($req), 'SELECT') === 0)
{
if (mysqli_num_rows($res))
{
while ($row = mysqli_fetch_assoc($res))
$ret[] = $row;
}
else $ret = array();
mysqli_free_result($res);
return $ret;
}
if (strpos($req, 'INSERT INTO') === 0)
return $mysqli->insert_id;
return $res;
}
Now I don't think I can use mysqli_real_escape_string because of the db-connector issue. Everything goes through that function. This means that avoiding sql injection is left in the hands of filter_vars before variables are mulched into SQL statements. I would like to parameterise my SQL statements and do it properly. But I'm just not sure what the most efficient way of doing it is in this case. Removing this function and converting everything to PDO would be very time consuming. There's a lot of code.
Here is a PDO-converted version of your function. It requires some helper classes:
// This class is your connection class using a singleton (like a global)
// You would need to populate your credentials in the connect method
class DatabaseConfig
{
private static $singleton;
public function __construct()
{
if(empty(self::$singleton))
self::$singleton = $this->connect();
return self::$singleton;
}
public function connect($host = "localhost", $username = "username", $password = "password", $database = "database")
{
// Create connection
$opts = array( PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC);
$conn = new PDO('mysql:host='.$host.';dbname='.$database, $username, $password,$opts);
return $conn;
}
}
// This is a query class that will run your sqls
class QueryEngine
{
private $results;
private static $singleton;
public function __construct()
{
if(empty(self::$singleton))
self::$singleton = $this;
return self::$singleton;
}
public function query($sql = false,$bind = false)
{
$this->results = 0;
$db = new DatabaseConfig();
try {
if(!empty($bind)) {
$query = $db ->connect()
->prepare($sql);
$query->execute($bind);
}
else {
$query = $db ->connect()
->query($sql);
}
$this->results = $query;
}
catch (PDOException $e)
{
die($e->getMessage());
}
return $this;
}
public function fetch()
{
while($row = $this->results->fetch())
$result[] = $row;
return (!empty($result))? $result : 0;
}
}
// This is your function down to the basics
// Error handling will be in the query class under try
function dbcommand($req,$bind = false)
{
// Create query instance
$qEngine = new QueryEngine();
// Run the query
$qEngine->query($req,$bind);
// If select, fetch array
if(strpos(strtoupper($req), 'SELECT') === 0)
return $qEngine->fetch();
// The query already ran, so you can return whatever you want
// For ease I am just returning true
elseif(strpos($req, 'INSERT INTO') === 0)
return true;
}
To use:
// Make sure it include the classes and function above
print_r(dbcommand("select * from `users` where `ID` = :0",array(":0"=>"1")));
This would give you something like (in my db obviously, the table and columns will be different for you):
Array
(
[0] => Array
(
[ID] => 1
[unique_id] => 20150203190700523616
[username] => tester
[password] => $2a$12$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
[first_name] => Ras
[last_name] => Clatt
[email] => ras#clatt.com
[usergroup] => 3
[user_status] => on
[reset_password] => $2y$10$xxxxxxxxxxxxxxxxxxx
[timestamp] => 2015-09-25 08:35:09
)
)
Related
I am in the learning phase of PHP. Could anyone please help me to write several functions:
ConDB($host, $user, $pwd) to connect to MySQL database via PDO.
RunSQL($sql) to execute statements (like CREATE, INSERT, UPDATE, DELETE). This function will use the ConDB() function to connect to the database and execute the $sql then close the connection automatically.
QuerySQL($sql) to query the database. It should return an array of recordsets that we can use further to display as a table on a page.
The RunSQL() and QuerySQL() functions should be available on any .php pages.
If something better can be done to achieve this will be a great help for me. Thanks.
For example I want to be able to write this:
$sql = “CREATE TABLE Persons(Person_ID int Auto_Incrememt PrimaryKey, Name varchar(255))”;
RunSQL($sql);
$sql = “SELECT * FROM Persons ORDER BY Person_ID ASC”;
$result = QuerySQL($sql)
Then with a while loop, we can fetch the data into a table.
I hope the idea is clear to you.
Nigel Ren's comment is correct but since I had the same problem a couple of years ago here is the link I used at that time. It also has usage examples.
Roll your own PDO class
Below you see my Database class that I created based on that example and some constants I use in that class as well as one simple usage example.
define("DB_USER", "yourUser");
define("DB_PASS", "yourPassword");
define("DB_HOST", "localhost");
define("DB_NAME", "yourDBName");
define("DB_CHARSET", "utf8mb4");
//create new database handler
$dbh = new Database();
//sample query
$dbh->query('SELECT var FROM table WHERE user_id = :id');
$dbh->bind(':id', $id );
$result = $dbh->resultsetAssoc(); // a two dimensional array is returned
foreach ($result as $values) {
//do something with $values["var"]
}
class Database
{
private $host = DB_HOST;
private $dbname = DB_NAME;
private $charset = DB_CHARSET;
private $user = DB_USER;
private $pass = DB_PASS;
private $dbh;
private $error;
private $stmt;
public function __construct() {
// Set DSN
$dsn = 'mysql:host=' . $this->host . ';dbname=' . $this->dbname . ';charset=' . $this->charset;
// Set options
$options = array(
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
);
// Create a new PDO instanace
try{
$this->dbh = new PDO($dsn, $this->user, $this->pass, $options);
}
catch(PDOException $e) {
$this->error = $e->getMessage();
file_put_contents('PDOErrors.txt', $this->error, FILE_APPEND);
die ("Database Connection Error");
}
}
public function query($query) {
$this->stmt = $this->dbh->prepare($query);
}
public function bind($param, $value, $type = null){
//determine type as input for bindValue
if (is_null($type)) {
switch (true) {
case is_int($value):
$type = PDO::PARAM_INT;
break;
case is_bool($value):
$type = PDO::PARAM_BOOL;
break;
case is_null($value):
$type = PDO::PARAM_NULL;
break;
default:
$type = PDO::PARAM_STR;
}
}
// PDO method bindValue (PDOStatement::bindValue)
$this->stmt->bindValue($param, $value, $type);
}
public function execute(){
return $this->stmt->execute();
}
// This is returned by resultset():
// Multidimemnsional array with column labeled values
// and numbered values
// Array
//(
// [0] => Array
// (
// [name] => pear
// [0] => pear
// [colour] => green
// [1] => green
// )
//
// [1] => Array
// (
// [name] => watermelon
// [0] => watermelon
// [colour] => pink
// [1] => pink
// )
//
//)
public function resultset(){
$this->execute();
return $this->stmt->fetchAll();
}
// This is returned by resultsetAssoc():
// Multidimemnsional array with column labeled values
// and NO numbered values
// Array
//(
// [0] => Array
// (
// [name] => pear
// [colour] => green
// )
//
// [1] => Array
// (
// [name] => watermelon
// [colour] => pink
// )
//
//)
public function resultsetAssoc(){
$this->execute();
return $this->stmt->fetchAll(PDO::FETCH_ASSOC);
}
// With single():
// The array has only one dimension!!!
public function single(){
$this->execute();
return $this->stmt->fetch();
}
// With singleAssoc():
// The array has only one dimension!!!
public function singleAssoc(){
$this->execute();
return $this->stmt->fetch(PDO::FETCH_ASSOC);
}
public function rowCount(){
return $this->stmt->rowCount();
}
public function lastInsertId(){
return $this->dbh->lastInsertId();
}
public function beginTransaction(){
return $this->dbh->beginTransaction();
}
public function endTransaction(){
return $this->dbh->commit();
}
public function inTransaction(){
return $this->dbh->inTransaction();
}
public function cancelTransaction(){
return $this->dbh->rollBack();
}
public function debugDumpParams(){
return $this->stmt->debugDumpParams();
}
// array is returned: [0] SQLSTATE, [1] some useless error code [2] error message
public function errorInfo() {
return $this->stmt->errorInfo();
}
}
This question already has an answer here:
How to use PDO connection in other classes?
(1 answer)
Closed 2 years ago.
I got this PDO database class
class clsDatabase{
// db settings
private $host = 'localhost';
private $user = 'test';
private $dbname = 'test';
private $pass = 'test1';
private $dbh;
private $error;
public function __construct(){
// Set DSN
$dsn = 'mysql: host=' . $this->host . ';dbname=' . $this->dbname;
// Set options
$options = array(
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES UTF8'
);
// Create a new PDO instanace
try{
$this->dbh = new PDO($dsn, $this->user, $this->pass, $options);
}
// Catch any errors
catch(PDOException $e){
$this->error = $e->getMessage();
echo $this->error;
exit;
}
}
public function query($query){
$this->stmt = $this->dbh->prepare($query);
}
}
I try to seperate my code in different classes, for example i got a clsDBUser which is connected to the clsUserController. I do this so i know what class uses what database code. My clsDBUser class looks like this
class clsDBUser extends clsDatabase {
// construct
public function __construct() {
parent::__construct();
}
// get users
public function getUsers($users_id){
$query = "
SELECT
email
FROM
users
WHERE
users_id = :users_id
";
$this->query($query);
$this->bind(':users_id', $users_id);
if($row = $this->single()){
$this->close();
return $row;
}
$this->close();
return false;
}
}
I am wondering if this is the way to go or am i creating a new database connection in every class right now? Because normally in PHP4 (yes i know old) i can't recognize i had to make a new database connection every time.
Do i need to improve this, how do i need to improve this?
You should take the road shown in the mr.void's answer. In short:
get rid of clsDatabase.
Create an instance of PDO.
pass it into clsDBLogin's property like it shown in mr.void's answer.
Then use this pdo instance in the form of $this->db->prepare() and so on
So it should be like
class clsDBLogin
{
public function __construct($db)
{
$this->db = $db;
}
public function validateLogin($email)
{
$email = trim($email);
// Check user in db to start verification
$query = 'SELECT * FROM users u, users_info ui
WHERE u.users_id = ui.users_id AND u.email = ?';
$stmt = $this->db->prepare($query);
$stmt->execute([$email]);
return $stmt->fetch();
}
}
$dsn = 'mysql: host=localhost;dbname=test;charset=utf8';
$options = array(
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
);
// Create a new PDO instanace
$pdo = new PDO($dsn, $this->user, $this->pass, $options);
$DBLogin = new clsDBLogin($pdo);
$user = $DBLogin->validateLogin($email);
Hey i would do Something like this
class DB {
// connectionStuff goes Here
}
class Model {
private $db
public function __construct($db) {
$this->db = $db;
}
}
Use:
$db = new DB("your connection stuff goes here");
$model = new Model($db);
$userModel = new UserModel($db);
$anotherModel = new AnotherModel($db);
Rebuild:
clsDB class with connection stuff only
class clsDB{
// db settings
private $host = 'localhost';
private $user = 'test';
private $dbname = 'test';
private $pass = 'test';
private $dbh;
private $error;
public function __construct(){
// Set DSN
$dsn = 'mysql: host=' . $this->host . ';dbname=' . $this->dbname;
// Set options
$options = array(
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES UTF8'
);
// Create a new PDO instanace
try{
$this->dbh = new PDO($dsn, $this->user, $this->pass, $options);
}
// Catch any errors
catch(PDOException $e){
$this->error = $e->getMessage();
echo $this->error;
exit;
}
}
}
clsDBLogin:
class clsDBLogin{
private $db;
public function __construct($db) {
$this->db = $db;
}
}
In index.php i do:
$clsDB = new clsDB();
$clsDBLogin = new clsDBLogin($clsDB);
In clsDBLogin i would do:
public function validateLogin($email){
$email = str_replace(' ', '', $email);
$email = strtolower($email);
// Check user in db to start verification
$query = '
SELECT
u.*, ui.*
FROM
users as u,
users_info as ui
WHERE
u.users_id = ui.users_id
AND
u.email = :email
';
$this->db->prepare($query);
$this->db->bindValue(':email', $email, PDO::PARAM_STR);
if($this->db->execute()){
if($row = $this->db->fetch(PDO::FETCH_ASSOC)){
return $row;
}
}
}
There are three layer here:
database connector: you can use pure PDO for this or a database abstraction layer library (Doctrine DBAL)
repository of entities: in other words, some kind of ORM. Doctrine provides advanced ORM functionality. Of course, you can write your own lightweight solution.
entity: can be a simple CRUD, an ActiveRecord or any other object representation of a logical record.
When we do this manually... First, don't extend these from each other. Generally: never extend a different layer from an other. Use Dependency Injection (DI) instead.
It's a very simple case of DI when you pass all the specific information (dependencies) as constructor parameters. My active-object-like example Entity just knows how an entity should be behave in general (at a key in a repository). For simplicity, I use raw SQL.
Repository class:
class Repository {
private $oPDO;
private $tableName;
private $keyFieldName;
public function __construct($oPDO, $tableName, $keyFieldName) {
$this->oPDO = $oPDO;
$this->tableName = $tableName;
$this->keyFieldName = $keyFieldName;
}
public function getPDO() {
return $this->oPDO;
}
public function getTableName() {
return $this->tableName;
}
public function getKeyFieldName() {
return $this->keyFieldName;
}
public function getEntity($id) {
return new Entity($this, $id);
}
public function createEntity() {
return new Entity($this, null);
}
}
Entity class:
class Entity implements ArrayAccess {
private $oRepository;
private $id;
private $record = null;
public function __construct($oRepository, $id) {
$this->oRepository = $oRepository;
$this->id = $id;
}
public function load($reload = false) {
if (!$this->record && !$this->id) {
return false;
}
if (!$reload && !is_null($this->record)) {
return true;
}
$quotedTableName = $this->quoteIdentifier($this->oRepository->getTableName());
$quotedKeyFieldName = $this->quoteIdentifier($this->oRepository->getKeyFieldName());
$selectSql = "SELECT * FROM {$quotedTableName} WHERE {$quotedKeyFieldName} = ?";
$oStatement = $this->oRepository->getPDO()->prepare($selectSql);
$this->bindParam($oStatement, 1, $this->id);
$oStatement->execute();
$result = $oStatement->fetch(PDO::FETCH_ASSOC);
if ($result === false || is_null($result)) {
return false;
}
$this->record = $result;
return true;
}
public function save() {
$oPDO = $this->oRepository->getPDO();
$tableName = $this->oRepository->getTableName();
$keyFieldName = $this->oRepository->getKeyFieldName();
$quotedTableName = $this->quoteIdentifier($tableName);
$quotedKeyFieldName = $this->quoteIdentifier($keyFieldName);
if (is_null($this->id)) {
$insertSql = "INSERT INTO {$quotedTableName} (";
$insertSql .= implode(", ", array_map([$this, "quoteIdentifier"], array_keys($this->record)));
$insertSql .= ") VALUES (";
$insertSql .= implode(", ", array_fill(0, count($this->record), "?"));
$insertSql .= ")";
$oStatement = $oPDO->prepare($insertSql);
$p = 1;
foreach ($this->record as $fieldName => $value) {
$this->bindParam($oStatement, $p, $value);
$p++;
}
if ($oStatement->execute()) {
$this->id = $oPDO->lastInsertId();
return true;
} else {
return false;
}
} else {
$updateSql = "UPDATE {$quotedTableName} SET ";
$updateSql .= implode(" = ?, ", array_map([$this, "quoteIdentifier"], array_keys($this->record)));
$updateSql .= " = ? WHERE {$quotedKeyFieldName} = ?";
$oStatement = $oPDO->prepare($updateSql);
$p = 1;
foreach ($this->record as $fieldName => $value) {
$this->bindParam($oStatement, $p, $value);
$p++;
}
$this->bindParam($oStatement, $p, $this->id);
if ($oStatement->execute()) {
if (isset($this->record[$keyFieldName])) {
$this->id = $this->record[$keyFieldName];
}
return true;
} else {
return false;
}
}
}
public function isExisting($reload = false) {
if (!$this->record && !$this->id) {
return false;
}
if (!$reload && !is_null($this->record)) {
return true;
}
$quotedTableName = $this->quoteIdentifier($this->oRepository->getTableName());
$quotedKeyFieldName = $this->quoteIdentifier($this->oRepository->getKeyFieldName());
$selectSql = "SELECT 1 FROM {$quotedTableName} WHERE {$quotedKeyFieldName} = ?";
$oStatement = $this->oRepository->getPDO()->prepare($selectSql);
$oStatement->bindParam(1, $this->id);
$oStatement->execute();
$result = $oStatement->fetch(PDO::FETCH_ASSOC);
if ($result === false || is_null($result)) {
return false;
}
return true;
}
public function getId() {
return $this->id;
}
public function getRecord() {
$this->load();
return $this->record;
}
public function offsetExists($offset) {
$this->load();
return isset($this->record[$offset]);
}
public function offsetGet($offset) {
$this->load();
return $this->record[$offset];
}
public function offsetSet($offset, $value) {
$this->load();
$this->record[$offset] = $value;
}
public function offsetUnset($offset) {
$this->load();
$this->record[$offset] = null;
}
private function quoteIdentifier($name) {
return "`" . str_replace("`", "``", $name) . "`";
}
private function bindParam($oStatement, $key, $value) {
$oStatement->bindParam($key, $value);
}
}
Usage:
$oRepo = new Repository($oPDO, "user", "user_id");
var_dump($oRepo->getEntity(2345235)->isExisting());
$oSameUser = $oRepo->getEntity(1);
var_dump($oSameUser->isExisting());
var_dump($oSameUser->getRecord());
$oNewUser = $oRepo->createEntity();
$oNewUser["username"] = "smith.john";
$oNewUser["password"] = password_hash("ihatesingletons", PASSWORD_DEFAULT);
$oNewUser["name"] = "John Smith";
$oNewUser->save();
$oNewUser["name"] = "John Jack Smith";
$oNewUser->save();
Of course, you can extend a MoreConcreteRepository from Repository and MoreConcreteEntity from Entity with specific behavior.
Simply don't extend an entity (clsDBUser) from a connection class (clsDatabase).
Use a singleton (or something more advanced pattern) for clsDatabase.
For example:
class clsDatabase {
static private $instance = null;
// some other private fields
private function __construct(/* parameters*/) {
// do it
}
public static function instance() {
if (is_null(self::$instance)) {
self::$instance = new self(/* pass any parameters */);
}
return self::$instance;
}
public function queryRow($query) {
$oStatement = $this->dbh->prepare($query);
// ...
return $row;
}
}
class clsDBUser {
public function getUser($id) {
$query = "...";
return $clsDatabase::instance()->queryRow($query);
}
}
I receive this error whenever I try to fetch something from my database using my PDO database wrapper:
Fatal error: Call to a member function fetch() on boolean in C:\xampp\htdocs\index.php on line 17
index.php
require_once 'class.db.php';
$mysql = [
'hostname' => '127.0.0.1',
'database' => 'lucidcms',
'username' => 'root',
'password' => 'test',
'charset' => 'utf8',
];
$db = new Database($mysql);
$u = "Sentinel";
$result = $db->prepare("SELECT `id` FROM `users` WHERE `username` = :u LIMIT 1")
->execute([':u' => $u])
->fetch();
echo $result;
class.db.php
<?php
class Database {
private static $stmt, $pdo;
public function __construct(array $mysql) {
if(is_null(self::$pdo)) {
try {
self::$pdo = new \PDO(
'mysql:dbname='.$mysql['database'].';host='.$mysql['hostname'].';charset='.$mysql['charset'],
$mysql['username'],
$mysql['password'],
[\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, \PDO::ATTR_PERSISTENT => true]
);
} catch(PDOException $e) {
die('An error occurred when trying to communicate with the database.');
//die($e->getMessage());
}
} else {
return new self;
}
}
public function __destruct() {
// Destruct the connection to DB once finished
try {
self::$pdo = null;
} catch(PDOException $e) {
die('An error occurred when trying to close the connection with the database.');
//die($e->getMessage());
}
}
public static function prepare($sql) {
self::$stmt = self::$pdo->prepare($sql);
return self::$stmt;
}
public static function execute($data = []) {
return self::$stmt->execute(isset($data) ? $data : null);
}
public static function count() {
return self::$stmt->rowCount();
}
public static function fetch() {
return self::$stmt->fetchColumn();
}
public static function fetchAll($type = \PDO::FETCH_ASSOC) {
return self::$stmt->fetch($type);
}
public static function lastId() {
return self::$pdo->lastInsertId();
}
}
I can't really seem to find out how to fix this error.
Any ideas of what I've done wrong?
EDIT, new working solution, but the coding is pretty messy:
<?php
class DB {
private static $host;
private static $db;
private static $dbuser;
private static $pass;
private static $char;
private static $pdo, $stmt;
public function data($mysql) {
self::$host = $mysql['hostname'];
self::$db = $mysql['database'];
self::$dbuser = $mysql['username'];
self::$pass = $mysql['password'];
self::$char = $mysql['charset'];
}
protected function __clone() {}
public static function connect() {
if(is_null(self::$pdo)) {
try {
self::$pdo = new \PDO(sprintf("mysql:host=%s;dbname=%s;",
self::$host,
self::$db),
self::$dbuser,
self::$pass,
[
\PDO::ATTR_EMULATE_PREPARES => false,
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION
]
);
} catch(PDOException $e) {
die('An error occurred when trying to communicate with the database.');
}
} else {
return self::$pdo;
}
}
public function __destruct() {
// Destruct the connection to DB once finished
try {
self::$pdo = null;
} catch(PDOException $e) {
die('An error occurred when trying to close the connection with the database.');
//die($e->getMessage());
}
}
public static function prepare($sql) {
DB::connect();
self::$stmt = self::$pdo->prepare($sql);
return new self;
}
public static function execute($data = []) {
DB::connect();
try {
self::$stmt->execute(isset($data) ? $data : null);
} catch(PDOException $e) {
die("Execution error: " . $e->getMessage());
}
return new self;
}
public static function fetchAll($type = \PDO::FETCH_BOTH) {
DB::connect();
return self::$stmt->fetch($type);
}
public static function fetch() {
DB::connect();
return self::$stmt->fetchColumn();
}
}
Any way to make the connect() and data() into a __construct() function?
There are many things you did wrong, (completely changed method names alone!) but the error message you get is clear: if you want to use method chaining, you should write every method just like prepare is written() - to make it return the statement.
However, this class couldn't be salvaged, as it will lead to nasty errors in production, due to its statefulness. Instead of this troublesome mess, let me suggest another solution, more lightweight and less error prone
class MyPDO extends PDO
{
public function run($sql, $args = NULL)
{
$stmt = $this->prepare($sql);
$stmt->execute($args);
return $stmt;
}
}
with this simple extension you will be able to use method chaining with your SELECT queries without the risk of messing your code up.
$result = $db->run("SELECT `id` FROM `users` WHERE `username` = :u LIMIT 1", [':u' => $u])
->fetch();
it is not that neat as your current approach but again - it doesn't bear its terrible drawbacks.
I have create a small class that handles my MySql queries.
However, Now I am trying to get the value of the last inserted id but it is not wokring for me
if my processQuery method in the class I do a prepare statement with any query like insert/update/remove
I have added this line $this->lastInsertId = $this->pdo->lastInsertId; which should give me the last inserted Id and stores it in the public variable called $lastInsertId then I can access it from my code outside.
How can I get the last inserted ID to work with this class??
Thanks
this is my class
<?php
class connection {
private $connString;
private $userName;
private $passCode;
private $server;
private $pdo;
private $errorMessage;
public $lastInsertId;
private $pdo_opt = array (
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
);
function __construct($dbName, $serverName = 'localhost'){
//sets credentials
$this->setConnectionCredentials($dbName, $serverName);
//start the connect
$this->startConnection();
}
function startConnection(){
$this->pdo = new PDO($this->connString, $this->userName, $this->passCode, $this->pdo_opt);
if( ! $this->pdo){
$this->errorMessage = 'Failed to connect to database. Please try to refresh this page in 1 minute. ';
$this->errorMessage .= 'However, if you continue to see this message please contact your system administrator.';
}
}
//this will close the PDO connection
public function endConnection(){
$this->pdo = null;
}
//return a dataset with the results
public function getDataSet($query, $data = NULL)
{
$cmd = $this->pdo->prepare( $query );
$cmd->execute($data);
return $cmd->fetchAll();
}
//return a dataset with the results
public function processQuery($query, $data = NULL)
{
$cmd = $this->pdo->prepare( $query );
$this->lastInsertId = $this->pdo->lastInsertId;
return $cmd->execute($data);
}
//this where you need to set new server credentials with a new case statment
function setConnectionCredentials($dbName, $serv){
switch($serv){
case 'BLAH':
$this->connString = 'mysql:host='.$serv.';dbname='.$dbName.';charset=utf8';
$this->userName = 'BLAH';
$this->passCode = 'BLAH';
break;
default:
$this->connString = 'mysql:host='.$serv.';dbname='.$dbName.';charset=utf8';
$this->userName = 'BLAH2';
$this->passCode = 'BLAH2';
break;
}
}
}
?>
You can add a method like this:
public function lastInsertId($name = NULL) {
if(!$this->pdo) {
throw new Exception('not connected');
}
return $this->pdo->lastInsertId($name);
}
It is just a wrapper around PDO::lastInsertId(). You won't need a local copy of the last insert id. If PDO isn't connected it will throw an Exception. You can change this to return FALSE; if this fit's your design more.
Use it like this:
$con = new connection('testdb');
$con->processQuery('INSERT INTO `foo` ....');
$lastInsertId = $con->lastInsertId();
I am currently using a combination of PHP, ORACLE, PDO, and JNDI to connect my application to the database. I am having trouble understanding the best approach to managing connection pools in an Object-Oriented approach. As a result, i'm getting max-connection warnings when I attempt to do bulk inserts (anything above 32 inserts (which my max-pool size is set to)).
Consider this example:
Main File:
//User uploads excel document which I parse into an array
$car = array();
foreach($array as $index => $data){
$car[$index] = new Car(null,$data["make"],$data["model"]);
$car[$index]->insert();
}
//return car array of objects
On the Class:
//Car Class
class Car{
protected $pkey;
protected $make;
protected $model;
protected $db;
public function __construct($pkey,$make,$model){
$this->pkey = $pkey;
if(isset($make) && ($make != '')){
$this->make = $make;
}else{
throw new Exception("Car must have make");
}
if(isset($model) && ($model != '')){
$this->model = $model;
}else{
throw new Exception("Car must have model");
}
$this->db = new Database();
}
public function insert(){
$sql = "INSERT INTO TABLE (...) VALUES (..)";
$data = array(
":make"=>$this->make,
":model"=>$this->model,
);
try{
$this->pkey = $this->db->insert($sql,$data);
return true;
}catch(Exception $err){
//catch errors
return false;
}
}
}
In this example, assuming max-pool is set to 32, any array greater than 32 will cause me to exceed the max-pool-size because each car object is stored with an active db connection. To fix this I tried implementing the following fixes to the class.
//Car Class
class Car{
protected $pkey;
protected $make;
protected $model;
protected $db;
public function __construct($pkey,$make,$model){
$this->pkey = $pkey;
if(isset($make) && ($make != '')){
$this->make = $make;
}else{
throw new Exception("Car must have make");
}
if(isset($model) && ($model != '')){
$this->model = $model;
}else{
throw new Exception("Car must have model");
}
//$this->db = new Database(); //Moved out of the constructor
}
public function insert(){
$this->establishDBConn();
$sql = "INSERT INTO TABLE (...) VALUES (...)";
$data = array(
":make"=>$this->make,
":model"=>$this->model,
);
try{
$this->pkey = $this->db->insert($sql,$data);
$this->closeDBConn();
return true;
}catch(Exception $err){
//catch errors
$this->closeDBConn();
return false;
}
}
protected function establishDBConn(){
if(!$this->db){
$this->db = new Database();
}
}
public function closeDBConn(){
if($this->db){
$this->db->close();
$this->db = null;
}
}
}
In theory this change should have enforced only maintaining an active connection during the actual insert process. However, with this change I continue to hit my max connection pool limit. As a last-ditch effort I moved all the insert logic out of the car class and created a bulk insert function. This function ignores the concept of an object, instead it just receives a data array which it loops through and inserts on a single data connection. This works, but I would love to find a way to fix my problem with in the constraints of Object-Oriented Programming.
Any suggestions on how I can improve my code in order to make more efficient use of objects and database connections?
For reference this is what my database class looks like:
class Database {
protected $conn;
protected $dbstr;
public function __construct() {
$this->conn = null;
$this->dbstr = "jndi connection string";
$this->connect();
}
public function connect(){
try{
$this->conn = new PDO($this->dbstr); // Used with jndi string
} catch (PDOException $e){
// print $e->getMessage();
}
return "";
}
public function insert($query, $data){
try{
$this->conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
/* Execute a prepared statement by passing an array of values */
$sth = $this->conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
$count = $sth->execute($data);
return $this->oracleLastInsertId($query);
}catch(PDOException $e){
throw new Exception($e->getMessage());
}
}
public function oracleLastInsertId($sqlQuery){
// Checks if query is an insert and gets table name
if( preg_match("/^INSERT[\t\n ]+INTO[\t\n ]+([a-z0-9\_\-]+)/is", $sqlQuery, $tablename) ){
// Gets this table's last sequence value
$query = "select ".$tablename[1]."_SEQ.currval AS last_value from dual";
try{
$temp_q_id = $this->conn->prepare($query);
$temp_q_id->execute();
if($temp_q_id){
$temp_result = $temp_q_id->fetch(PDO::FETCH_ASSOC);
return ( $temp_result ) ? $temp_result['LAST_VALUE'] : false;
}
}catch(Exception $err){
throw new Exception($err->getMessage());
}
}
return false;
}
public function close(){
$this->conn = null;
}
}
The correct approach seems to be to use a singleton-based database class as such:
class Database {
protected $conn;
protected $dbstr;
// keep the one and only instance of the Database object in this variable
protected static $instance;
// visibility changed from public to private to disallow dynamic instances
private function __construct() {
$this->conn = null;
$this->dbstr = "jndi connection string";
$this->connect();
}
// added this method
public static function getInstance() {
if (!isset(self::$instance)) {
self::$instance = new Database();
}
return self::$instance;
}
// everything below this comment is as it was; I made no changes here
public function connect(){
try{
$this->conn = new PDO($this->dbstr); // Used with jndi string
} catch (PDOException $e){
// print $e->getMessage();
}
return "";
}
public function insert($query, $data){
try{
$this->conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
/* Execute a prepared statement by passing an array of values */
$sth = $this->conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
$count = $sth->execute($data);
return $this->oracleLastInsertId($query);
}catch(PDOException $e){
throw new Exception($e->getMessage());
}
}
public function oracleLastInsertId($sqlQuery){
// Checks if query is an insert and gets table name
if( preg_match("/^INSERT[\t\n ]+INTO[\t\n ]+([a-z0-9\_\-]+)/is", $sqlQuery, $tablename) ){
// Gets this table's last sequence value
$query = "select ".$tablename[1]."_SEQ.currval AS last_value from dual";
try{
$temp_q_id = $this->conn->prepare($query);
$temp_q_id->execute();
if($temp_q_id){
$temp_result = $temp_q_id->fetch(PDO::FETCH_ASSOC);
return ( $temp_result ) ? $temp_result['LAST_VALUE'] : false;
}
}catch(Exception $err){
throw new Exception($err->getMessage());
}
}
return false;
}
public function close(){
$this->conn = null;
}
}