I've spent the last several hours trying to find an answer to the "best", most logical, etc way to write a php database class to simultaneously connect to one postgresql db and one mysql db. Also, I'd like to adopt a Dependency Injection design but am new to that whole concept.
So far I've come up with...
class Database {
public function PgSqlConnect() {
/* Connect to database */
$host = 'localhost';
$dbname = '---';
$user = '---';
$pass = '---';
$timeout = 5; /* seconds */
try {
$pgsql_dbh = new PDO("pgsql:host=$host; dbname=$dbname", $user, $pass);
$pgsql_dbh->setAttribute( PDO::ATTR_TIMEOUT, $timeout );
$pgsql_dbh->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
return $pgsql_dbh;
} catch( PDOException $e ) {
echo 'Unable to connect to database: ' . $e->getMessage();
}
}
public function MySqlConnect() {
/* Connect to database */
$host = 'localhost';
$dbname = '---';
$user = '---';
$pass = '---';
$timeout = 5; /* seconds */
try {
$mysql_dbh = new PDO("mysql:host=$host; dbname=$dbname", $user, $pass);
$mysql_dbh->setAttribute( PDO::ATTR_TIMEOUT, $timeout );
$mysql_dbh->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
return $mysql_dbh;
} catch( PDOException $e ) {
echo 'Unable to connect to database: ' . $e->getMessage();
}
}
}
Obviously the duplicated code violates the DRY approach. I know and have seen many examples of multiple db connections, but most deal with same driver and don't provide DI capability.
I should also add that I've considered placing the connection details into the Database class constructor as...
$driver = 'mysql';
...
$mysqldb = new Database($driver,$un,$pw,...);
$driver = 'pgsql';
...
$pgsqldb = new Database($driver,$un,$pw,...);
but I don't know if that is really a good idea nor how well it would work with DI.
Many thanks!
You should create an interface first for all the DB operations.
interface IDatabase
{
function connect();
function query();
...
}
Then have different driver classes implementing this interface
class MySQLDB implements IDatabase
{
}
class PGSQLDB implements IDatabase
{
}
This way you can easily use dependency injection.
class Test
{
private $db;
function __construct(IDatabase $db)
{
$this->db = $db;
}
}
You can call it as:
$mysqldb = new MySQLDB();
$test = new Test($mysqldb);
or
$pgsqldb = new PGSQLDB();
$test = new Test($pgsqldb);
To avoid duplicated code you can extend an abstract class
abstract class AbstractDb {
public function connect() {
// common code to avoid duplication
echo 'connected!';
}
abstract public function escapeField();
abstract public function escapeValue();
}
class MySQL extends AbstractDb {
public function escapeField() {
// Db-specific method
}
public function escapeValue() {
// Db-specific method
}
}
$db = new MySQL;
$db->connect();
Or use composition, and let the Db class use a different driver for db-specific methods
interface IDriver {
public function escapeField();
public function escapeValue();
}
class MySQLDriver implements IDriver {
public function escapeField() {
// Db-specific method
}
public function escapeValue() {
// Db-specific method
}
}
class Db {
public function __construct($driver) {
$this->driver = $driver;
}
public function connect() {
// common code here ? idk, it's just an example
echo 'connect!';
}
// this method is db-specific, so we call the driver
public function escapeField($field) {
return $this->driver->escapeField($field);
}
public function escapeValue() {
// same here
}
}
$db = new Db(new MySQLDriver);
$db->connect();
In php 5.4 there will be traits, so there will be more approaches to avoid code duplication.
Related
I'm trying to return $db connection from function, so that I could use it in another function. But someone functions don't understand $db variable.
function dbConnection(){
$dbhost = 'localhost';
$dbuser = '...';
$dbpass = '...';
$dbname = '...';
try {
$db = new PDO("mysql:host=$dbhost;dbname=$dbname;charset=UTF8", $dbuser, $dbpass);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
return $db;
} catch (PDOException $e){
echo 'Connection failed: ' . $e->getMessage();
}
}
Then use it in all of my functions.
function getCurrentFile(){
dbConnection();
$stmt = $db->prepare(...);
$stmt->execute();
}
Are there more preferable way to handle db connections? I used to require_once config.php(where is only db connection) file every time, but I don't want to do it anymore.
One of the options is define singletone class providing database object:
// File: DbConection.php
class DbConnection
{
/**
* #var \PDO
*/
private static $pdo;
private function __constructor()
{
}
public static function getInstance()
{
if(null === self::$pdo) {
self::$pdo = new \PDO('....);
}
return $self::pdo;
}
private function __clone()
{
}
private function __wakeup()
{
}
}
If you are using composer tool, add this line to composer.json file:
"autoload": {
"classmap": [
"path_to_your_file/DbConnection.php"
]
},
In your project, on bootstrap include generated by composer vendor/autoload.php file and start using class by:
use DbConnection;
DbConnection::getInstance();
in whole projec.
Write down your db connection code in class and create object to use everywhere else in your system.
Having trouble understanding classes and inheritance:
core.php:
$servername = "****";
$database = "****";
$username = "****";
$password = "****";
try {
$pdo = new PDO("mysql:host=$servername;dbname=$database", $username, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch(PDOException $e) {
echo "Connection failed: " . $e->getMessage();
}
class Database {
protected $pdo;
public function __construct($pdo) {
$this->pdo = $pdo;
}
}
class User extends Database {
private $ip;
private $sessionId;
public function __construct($ip, $sessionId) {
$this->ip = $ip;
$this->sessionId = $sessionId;
}
public function getSessionInfo () {
$stmt = $this->pdo->prepare(".."); <-- error here
....
}
}
When calling:
require_once 'api/core.php';
$database = new Database($pdo);
$user = new User($_SERVER['REMOTE_ADDR'], $_SESSION['info']['id']);
In this contest $database, and $user variables are not related to each other:
require_once 'api/core.php';
$database = new Database($pdo);
$user = new User($_SERVER['REMOTE_ADDR'], $_SESSION['info']['id']);
Thus, calling prepare() on $user won't work.
You need a mechanism, at least like this , although not a good practice to assign Database to a User:
$user->setDatabase($database);
Instead create a static Database object, initiate it before User initiation, and call it statically within User object, or any other object, make it available for all objects.
A quick fix would look like this, where User doesn't extend Database, because it's wrong. User is not a Database.
$database = new Database();
$user = new User();
$user->setDatabase($database); //sets $db variable inside User
//User.php
namespace MyApp;
class User{
private Database $db;
public function setDatabase($db){
$this->db = $db;
}
public function doSomething(){
$this->db->getPdo()->prepare('..');
}
}
//Database.php
namespace MyApp;
class Database{
private $pdo; //returns PDO object
function __construct(){
//create pdo connection
$this->pdo = ..
}
function getPdo(){
return $this->pdo;
}
}
Database should be injected to objects or used by objects, you shouldn't be extending Database just to have it. If you want to do it properly, in an object-oriented way.
Remember PHP doesn't allow multiple inheritances by extend. Tomorrow, you might want to have a Person class that every User will extend, but since you did it wrong in the beginning, and wasting precious extend on Database, it won't be possible. And by not having a control of how many database instances you have created, you will run into issues. You need to know for sure that you have only a single connection object for one database, if of course the opposite is a must - which in your case I doubt.
Of course this will change if you have multiple database requirements, and more sophisticated app structure.
You are receiving this error because User Instance has pdo empty. try this code
$servername = "****";
$database = "****";
$username = "****";
$password = "****";
try {
$pdo = new PDO("mysql:host=$servername;dbname=$database", $username, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch(PDOException $e) {
echo "Connection failed: " . $e->getMessage();
}
class Database {
protected $pdo;
public function __construct($pdo) {
$this->pdo = $pdo;
}
}
class User extends Database {
private $ip;
private $sessionId;
public function __construct($pdo, $ip, $sessionId) {\
parent::__construct($pdo)
$this->ip = $ip;
$this->sessionId = $sessionId;
}
public function getSessionInfo () {
$stmt = $this->pdo->prepare("..");
....
}
}
then
require_once 'api/core.php';
$user = new User($pdo, $_SERVER['REMOTE_ADDR'], $_SESSION['info']['id']);
hope it helps.
Below is the solution to my question. I didn't have my class written wrong. I accidentally used a "die" function inside my master DB class that made all my queries fail. I was tracking down the wrong problem.
I hope you guys find this class example useful. It's clean, and very useful for wrapping SQL calls in a single location for multiple databases. By allowing the extension, you can track your calls much easier my var name.
class A extends DB {
protected $connection;
function __construct()
{
$this->db_host = "server.com:3327";
$this->db_name = "db_name_one";
$this->db_username = "root";
$this->db_password = 'pw';
// .. equals all the setup vars
parent::__construct();
}
}
class B extends DB {
function __construct()
{
$this->db_host = "server.com:3327";
$this->db_name = "db_name_two";
$this->db_username = "root";
$this->db_password = 'pw';
// .. equals all the setup vars
parent::__construct();
}
}
class DB {
function __construct()
{
$this->connect()
}
public function connect()
if (!$connection = # mysql_connect ($this->db_host,$this->db_username,$this->db_password,$this->second_flag))
die ('I cannot connect to the database because: ' . mysql_error());
if (!mysql_selectdb($this->db_name,$connection))
$this->showerror();
return $connection;
}
public function executeSQL($query)
{
$results = mysql_query($query,$this->connection);
if (!$results) {
die(...);
}
return $results;
}
}
$db1 = new A();
$db2 = new B();
$db1->executeSQL("select * from table");
I don't know if there's a legitimate reason to extend the DB class here. Unless those extending classes add something to the DB (e.g. they adapt it to a different database), then they have nothing to do with being a DB as such. Just because they use a DB doesn't mean they should extend one.
The simplest thing to do is: make one class that handles your database interaction. When you make a new instance of that class, it establishes a new connection to the database. This way you can have as many independent connections as you want. You then simply pass those instances to whoever needs them:
class DB {
protected $connection;
public function __construct() {
$this->connection = mysql_connect(..);
if (!$this->connection) {
throw new Exception(..);
}
}
..
}
class A {
protected $db;
public function __construct(DB $db) {
$this->db = $db;
}
..
}
$db = new DB;
$a = new A($db);
I have been recently toying with dynamic PDO connection methods. However I run into some problems when I started to use other classes.
Why can't I connect to database in Admin class with method that has been constructed in Server class?
I have tried many solutions. This one seemed most logical to me ...
How do I make it work so that I wouldn't have to construct connection in every class?
class Server
{
private $hostdb = 'blah';
private $namedb = 'blah';
private $userdb = 'blah';
private $passdb = 'blah';
public static $conn;
public $errorMessage = 'If you read this text, contact web administrator and tell him about your problem.';
public function __construct()
{
$this->connect();
}
public function connect()
{
try {
$this->conn = new PDO("mysql:host=$this->hostdb; dbname=$this->namedb", $this->userdb, $this->passdb, array(PDO::ATTR_PERSISTENT => true));
$this->conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->conn->exec("SET CHARACTER SET utf8");
} catch (PDOException $e) {
echo 'Connection failed: ' . $e->getMessage();
}
}
}
And the Admin class:
class Admin extends User
{
function someFunction($table)
{
try {
$sql = "SELECT * FROM $table";
//I want to change this line so that my connection would work
$result = Server::$conn->query($sql);
while ($row = $result->fetch(PDO::FETCH_NUM)) {
//Do something
}
} catch (PDOException $e) {
//Show when debugging
//echo $e->getMessage();
echo Server::errorMessage;
}
}
}
I have instantiated both Server, User and Admin class in config.req.php file.
When I changed "Server::$conn->" to "static::$conn->" it still gave me an error.
Make sure you have instantiated Server at least once in the Request. Otherwise, connect() will never be called. Also, $this->conn will create a new public instance property. Static properties need to be set with static::$conn or self::$conn.
So change your connect() method to
public function connect()
{
try {
self::$conn = new PDO("arguments …"));
self::$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
self::$conn->exec("SET CHARACTER SET utf8");
// … shortened for brevity
On a side note, why not just use Dependency Injection? That makes for more maintainable and testable designs, e.g. keep the $this->conn and remove all the static stuff instead (in addition to the constructor) for something like
class Server
{
private $hostdb = 'blah';
private $namedb = 'blah';
private $userdb = 'blah';
private $passdb = 'blah';
private $conn;
public function getConnection()
{
if (!isset($this->conn)) {
$this->connect();
}
return $this->connection;
}
And then for your Admin class:
class Admin extends User
{
private $server;
public function __construct(Server $server)
{
$this->server = $server;
}
function someFunction($table)
{
try {
$sql = "SELECT * FROM $table";
$result = $this->server->getConnection()->query($sql);
On another side note:
$sql = "SELECT * FROM $table"
Interpolating strings into a query makes you open to SQL Injection attacks. Use prepared statements instead.
I want to be able to make classes which extend the MySQLi class to perform all its SQL queries.
$mysql = new mysqli('localhost', 'root', 'password', 'database') or die('error connecting to the database');
I dont know how to do this without globalising the $mysql object to use in my other methods or classes.
class Blog {
public function comment() {
global $mysql;
//rest here
}
}
Any help would be appreciated.
Thanks.
I was working on something similar. I'm happy about this singleton class that encapsulates the database login.
<?php
class db extends mysqli
{
protected static $instance;
protected static $options = array();
private function __construct() {
$o = self::$options;
// turn of error reporting
mysqli_report(MYSQLI_REPORT_OFF);
// connect to database
#parent::__construct(isset($o['host']) ? $o['host'] : 'localhost',
isset($o['user']) ? $o['user'] : 'root',
isset($o['pass']) ? $o['pass'] : '',
isset($o['dbname']) ? $o['dbname'] : 'world',
isset($o['port']) ? $o['port'] : 3306,
isset($o['sock']) ? $o['sock'] : false );
// check if a connection established
if( mysqli_connect_errno() ) {
throw new exception(mysqli_connect_error(), mysqli_connect_errno());
}
}
public static function getInstance() {
if( !self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
public static function setOptions( array $opt ) {
self::$options = array_merge(self::$options, $opt);
}
public function query($query) {
if( !$this->real_query($query) ) {
throw new exception( $this->error, $this->errno );
}
$result = new mysqli_result($this);
return $result;
}
public function prepare($query) {
$stmt = new mysqli_stmt($this, $query);
return $stmt;
}
}
To use you can have something like this:
<?php
require "db.class.php";
$sql = db::getInstance();
$result = $sql->query("select * from city");
/* Fetch the results of the query */
while( $row = $result->fetch_assoc() ){
printf("%s (%s)\n", $row['Name'], $row['Population']);
}
?>
My suggestion is to create a Singleton DataAccess class, instantiate that class in a global config file and call it in your Blog class like $query = DataAccess::query("SELECT * FROM blog WHERE id = ".$id).
Look into the Singleton pattern, it's a pretty easy to understand designpattern. Perfect for this situation.
Your DataAccess class can have several methods like query, fetchAssoc, numRows, checkUniqueValue, transactionStart, transactionCommit, transactionRollback etc etc. Those function could also be setup as an Interface which gets implemented by the DataAccess class. That way you can easily extend your DataAccess class for multiple database management systems.
The above pretty much describes my DataAccess model.
You can use PHP's extends keyword just for any other class:
class MyCustomSql extends mysqli {
public function __construct($host, $user, $password, $database) {
parent::__construct($host, $user, $password, $database);
}
public function someOtherMethod() {
}
}
$sql = new MyCustomSql('localhost', 'root', 'password', 'database') or die('Cannot connect!');
or better use object aggregation instead of inheritance:
class MySqlManipulator {
private $db;
public function __construct($host, $user, $password, $database) {
$this->db = new mysqli($host, $user, $password, $database);
}
public function someOtherMethod() {
return $this->db->query("SELECT * FROM blah_blah");
}
}
$mysqlmanipulator = new MySqlManipulator('localhost', 'root', 'password', 'database') or die('Cannot connect!');
My standard method is to make a singleton class that acts as the database accessor, and a base class that everything requiring such access inherits from.
So:
class Base {
protected $db;
function __construct(){
$this->db= MyDBSingleton::get_link();
//any other "global" vars you might want
}
}
class myClass extends Base {
function __construct($var) {
parent::__construct();// runs Base constructor
$this->init($var);
}
function init($id) {
$id=(int) $id;
$this->db->query("SELECT * FROM mytable WHERE id=$id");
//etc.
}
}
Have a look at PDO, which throw exceptions for you to catch if a query fails. It's widely used and tested so you shouldn't have a problem finding existing solutions whilst using it.
To inject it into your blog class:
class Blog {
private $_db;
public function __construct(PDO $db) {
$this->_db = $db
}
public function comment() {
return $this->_db->query(/*something*/);
}
}