I am a bit puzzled here, I am easing myself into oop and struggling a little. I have beenusing Aura SQL (php) and have achieved pretty much what I need, but, it has become necessary to connect to more than one DB to update some legacy code I have been passed.
class Core {
public $pdo;
private static $instance;
private function __construct() {
$this->pdo = new ExtendedPdo(
'mysql:host=localhost;charset=utf8;dbname=db',
'user',
'pass'
);
$this->pdo->setAttribute(PDO::MYSQL_ATTR_INIT_COMMAND, 'SET NAMES utf8');
}
public static function getInstance() {
if (!isset(self::$instance)) {
$object = __CLASS__;
self::$instance = new $object;
}
return self::$instance;
}
}
I have tried to amend by passing the values in via the constructor, but, it seems to be ignored eg:
private function __construct($db_name) {
if !empty($db_name)) { $this->db = $db_name; } else { $this->db = 'default_db'; }
$this->pdo = new ExtendedPdo(
'mysql:host=localhost;charset=utf8;dbname='.$this->db.'',
....
Normally you would use getInstance(); but I have been trying instantiating a new object to access the other databases.
Thanks for any advice.
Please checkout the ConnectionLocator via which you can create default connection, and rest of the connections. Taking code snippet from the readme.
<?php
use Aura\Sql\ConnectionLocator;
use Aura\Sql\ExtendedPdo;
// default connection
$default = function () {
return new ExtendedPdo(
'mysql:host=default.db.localhost;dbname=database',
'username',
'password'
);
};
// read connections
$read = array(
'slave1' => function () {
return new ExtendedPdo(
'mysql:host=slave1.db.localhost;dbname=database',
'username',
'password'
);
},
'slave2' => function () {
return new ExtendedPdo(
'mysql:host=slave2.db.localhost;dbname=database',
'username',
'password'
);
},
'slave3' => function () {
return new ExtendedPdo(
'mysql:host=slave3.db.localhost;dbname=database',
'username',
'password'
);
},
);
// write connection
$write = array(
'master' => function () {
return new ExtendedPdo(
'mysql:host=master.db.localhost;dbname=database',
'username',
'password'
);
},
);
// configure locator at construction time
$connections = new ConnectionLocator($default, $read, $write);
$default_connection = $connections->getDefault();
$read_slave1_connection = $connections->getRead('slave1');
$read_any_connection = $connections->getRead();
$write_connection = $connections->getWrite();
Related
The discussion of using a Singleton or not is out of scope for this question.
That said, I've found online multiple database connection classes using PDO and following the singleton pattern.
Here is one for example :
<?php
define('DB_HOST', 'localhost');
define('DB_NAME', 'test');
define('DB_USER', 'root');
define('DB_PASS', '');
define('DB_CHAR', 'utf8');
class DB
{
protected static $instance = null;
protected function __construct() {}
protected function __clone() {}
public static function instance()
{
if (self::$instance === null)
{
$opt = array(
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => FALSE,
);
$dsn = 'mysql:host='.DB_HOST.';dbname='.DB_NAME.';charset='.DB_CHAR;
self::$instance = new PDO($dsn, DB_USER, DB_PASS, $opt);
}
return self::$instance;
}
public static function __callStatic($method, $args)
{
return call_user_func_array(array(self::instance(), $method), $args);
}
public static function run($sql, $args = [])
{
if (!$args)
{
return self::instance()->query($sql);
}
$stmt = self::instance()->prepare($sql);
$stmt->execute($args);
return $stmt;
}
}
However, according to this Stackoverflow answer Is it necessary to close PDO connections
, some users suggest that it's a good practice to close all connections at the end of the script.
So:
1. Why does no one include a destroy/close method?
2. How would it be done right (destroying a PDO inside a singleton?)
My approach would have been:
class DB
{
// ... the whole singleton
public static function close()
{
self::$instance = null;
return null;
}
}
$pdo = DB::getInstance();
// do stuff
$pdo = DB::close($pdo);
I am new to using PHPUnit and i am trying to test function getAllTasks() which need to fetch all tasks from database. I tried everything but i am just making my code worse. So please help me to solve the problem. TaskTest.php is something i tried to make test but it dont works. And sure if there are better ways to do something, i like to learn new stuff too. Here is my code:
EDIT: I changed code for TaskTest.php and i managed to get test pass. Can someone please tell me if this is good way to test this function, or there are better ways? Thanks!
Task.php
<?php
require_once 'Database.php';
class Task {
private $db;
public function __construct() {
$this->db = new Database;
}
public function getAllTasks() {
$this->db->query('SELECT * FROM tasks');
$results = $this->db->resultSet();
return $results;
}
}
Database.php
<?php
class Database {
private $host = 'localhost';
private $user = 'root';
private $pass = '123456';
private $dbname = 'todolist';
private $dbh;
private $stmt;
private $error;
public function __construct(){
// Set DSN
$dsn = 'mysql:host=' . $this->host . ';dbname=' . $this->dbname;
$options = array(
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
);
// Create PDO instance
try {
$this->dbh = new PDO($dsn, $this->user, $this->pass, $options);
} catch(PDOException $e){
$this->error = $e->getMessage();
echo $this->error;
}
}
public function query($sql){
$this->stmt = $this->dbh->prepare($sql);
$this->execute();
}
public function execute(){
return $this->stmt->execute();
}
public function resultSet(){
$this->execute();
return $this->stmt->fetchAll(PDO::FETCH_ASSOC);
}
}
TaskTest.php
<?php
require_once './src/Task.php';
require_once './src/Database.php';
use PHPUnit\Framework\TestCase;
class TaskTest extends TestCase {
public function testGetAllTasks() {
$table = array(
array(
'task_id' => '1',
'task_desc' => 'Task One Test'
),
array(
'task_id' => '2',
'task_desc' => 'Task Two Test'
)
);
$dbase = $this->getMockBuilder('Database')
->getMock();
$dbase->method('resultSet')
->will($this->returnValue($table));
$expectedResult = [
'task_id' => '1',
'task_desc' => 'Task One Test',
];
$task = new Task();
$actualResult = $task->getAllTasks();
$this->assertEquals($expectedResult, $actualResult[0]);
}
}
You pass the mock to the Task class constructor, but it doesn't do anything with it.
$task = new Task($resultSetMock);
Updated the code so that it will be used:
class Task {
private $db;
public function __construct( ?Database $db = null ) {
// set the db if none is provided
if( is_null($db) )
{
$db = new Database;
}
$this->db = $db;
}
// ...
}
I have created a database class which is throwing an error of
Undefined variable: options in C:path/ on line 21
and I cant seem to figure out why.
class Db
{
private static $instance = null;
private $options = array(
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
);
private function __construct() {}
private function __clone() {}
public static function getInstance()
{
if (!isset(self::$instance)) {
self::$instance = new PDO('mysql:host=localhost;dbname=mydb', 'root', '', $options);
}
return self::$instance;
}
}
Your $options doesn't exist. You should reference the defined one as $this->options, but that's not a solution for you as you are using it in a static function: the $this context is not available.
You could just define your options in that static method I guess, like below. Not the prettiest solution as now your settings are hidden in your creation-method, but as an example:
class Db
{
private static $instance = null;
private function __construct() {}
private function __clone() {}
public static function getInstance()
{
$options = array(
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
);
if (!isset(self::$instance)) {
self::$instance = new PDO('mysql:host=localhost;dbname=mydb', 'root', '', $options);
}
return self::$instance;
}
}
The error by the way is quite clear: you are using a variable $options that doesn't exist at that point so you get an error that says it's not defined.
In this question here on S.O, the accepted answer suggests to use both anonymous function and factory pattern for dealing with PDO connection. I believe the anonymous function is used in case a connection to a different database needs to be established, a different function will be defined for that. In that case, will it be alright to move the anonymous function to the factory class itself for just a single database?
This is what I had in mind
class StructureFactory
{
protected $provider = null;
protected $connection = null;
public function __construct( )
{
$this->provider = function() {
$instance = new PDO('mysql:......;charset=utf8', 'username', 'password');
$instance->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$instance->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
return $instance;
};
}
public function create( $name )
{
if ( $this->connection === null )
{
$this->connection = call_user_func( $this->provider );
}
return new $name( $this->connection );
}
}
Also, even with this approach why not just passing the PDO parameters to the constructor achieve what the original answer was trying to achieve, i.e to establish different connections?
Something like:
class StructureFactory
{
protected $provider = null;
protected $connection = null;
public function __construct( $PDO_Params )
{
$this->provider = function() {
$instance = new PDO($PDO_Params["dsn"], $PDO_Params["username"], $PDO_Params["password"]);
$instance->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$instance->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
return $instance;
};
}
public function create( $name)
{
if ( $this->connection === null )
{
$this->connection = call_user_func( $this->provider );
}
return new $name( $this->connection );
}
}
No. Because that way you loos any benefits from dependency injection.
The modification, that you could to is: inject an initialized PDO instance directly in the factory, instead of using this lazy-loading approach. Kinda like in this answer.
I would also recommend watching this lecture.
Is it possible to keep all my database related configuration (hostnames, usernames, passwords, and databases) as well as the function to connect to and select the correct database in a separate class?
I tried something like this:
class Database
{
var $config = array(
'username' => 'someuser',
'password' => 'somepassword',
'hostname' => 'some_remote_host',
'database' => 'a_database'
);
function __construct() {
$this->connect();
}
function connect() {
$db = $this->config;
$conn = mysql_connect($db['hostname'], $db['username'], $db['password']);
if(!$conn) {
die("Cannot connect to database server");
}
if(!mysql_select_db($db['database'])) {
die("Cannot select database");
}
}
}
And then in another class I would use in the classes __construct function:
require_once('database.php');
var $db_conn = new Database();
But this doesnt save the connection, it ends up defaulting to the servers local db connection. Or do I have to do the database commands everytime before I execute some database commands?
I modified your class to work as you seem to be expecting it to:
<?php
class Database
{
var $conn = null;
var $config = array(
'username' => 'someuser',
'password' => 'somepassword',
'hostname' => 'some_remote_host',
'database' => 'a_database'
);
function __construct() {
$this->connect();
}
function connect() {
if (is_null($this->conn)) {
$db = $this->config;
$this->conn = mysql_connect($db['hostname'], $db['username'], $db['password']);
if(!$this->conn) {
die("Cannot connect to database server");
}
if(!mysql_select_db($db['database'])) {
die("Cannot select database");
}
}
return $this->conn;
}
}
Usage:
$db = new Database();
$conn = $db->connect();
Note that you can call connect() as many times as you like and it will use the current connection, or create one if it doesn't exist. This is a good thing.
Also, note that each time you instantiate a Database object (using new) you will be creating a new connection to the database. I suggest you look into implementing your Database class as a Singleton or storing it in a Registry for global access.
You can also do it the dirty way and shove it in $GLOBALS.
Edit
I took the liberty of modifying your class to implement the Singleton pattern, and follow the PHP5 OOP conventions.
<?php
class Database
{
protected static $_instance = null;
protected $_conn = null;
protected $_config = array(
'username' => 'someuser',
'password' => 'somepassword',
'hostname' => 'some_remote_host',
'database' => 'a_database'
);
protected function __construct() {
}
public static function getInstance()
{
if (null === self::$_instance) {
self::$_instance = new self();
}
return self::$_instance;
}
public function getConnection() {
if (is_null($this->_conn)) {
$db = $this->_config;
$this->_conn = mysql_connect($db['hostname'], $db['username'], $db['password']);
if(!$this->_conn) {
die("Cannot connect to database server");
}
if(!mysql_select_db($db['database'])) {
die("Cannot select database");
}
}
return $this->_conn;
}
public function query($query) {
$conn = $this->getConnection();
return mysql_query($query, $conn);
}
}
Usage:
$res = Database::getInstance()->query("SELECT * FROM foo;");
or
$db = Database::getInstance();
$db->query("UPDATE foo");
$db->query("DELETE FROM foo");
You can certainly keep your connection info in a separate file.
Just save your connection object - $conn in your connect() function - in a class variable. You'll then be able to reuse it across calls.
In your method connect() $conn is only a local variable that only exists in the scope of that method. As soon as the method returns there will be no (other) reference to the connection resource and it will be collected/disposed.
You'll need at least
$this->conn = mysql_connect(...)
Here comes the singleton with PDO example. Thanks to #hodobave
<?php
/**
* DB Connection class
* Singleton pattern
* An single instance of DB connection is created
**/
class Db{
protected static $_instance = null;
protected $_conn;
protected $_config = [
'username' => 'root',
'password' => '',
'hostname' => 'localhost',
'database' => 'news',
];
protected function __construct(){
}
public function getInstance(){
if(null === self::$_instance){
self::$_instance = new self();
}
return self::$_instance;
}
public function getConnection(){
if(is_null($this->_conn)){
//connect here
$db = $this->_config;
$dsn = "mysql:host={$db['hostname']};dbname={$db['database']}";
$this->_conn = new PDO($dsn, $db['username'], $db['password']);
}
return $this->_conn;
}
public function query($sql){
$args = func_get_args();
array_shift($args);
$statement = $this->getConnection()->prepare($sql);
$statement->execute($args);
return $statement->fetchAll(PDO::FETCH_OBJ);
}
}
$res = Db::getInstance();
$users = $res->query("SELECT * FROM `user` WHERE id=?",1);
print_r($users);
?>