Properly pass connection to connstructor PDO - php

I have spent several days reading different tutorials posts etc either info is outdated or appreciated.
I have a database connection class very simple
namespace App\Database;
use PDO;
use PDOException;
/**
* #desc Connection to the database
**/
class Database
{
protected string $dbhost = DATABASE_HOST;
protected string $dbuser = DATABASE_USER;
protected string $dbpass = DATABASE_PASS;
protected string $dbname = DATABASE_NAME;
protected PDO $conn;
public function __construct()
{
// Set DSN
$dsn = 'mysql:host=' . $this->dbhost . ';dbname=' . $this->dbname . ';charset=' . $this->charset;
$options = array(
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
);
// Create PDO instance
try {
$this->conn = new PDO($dsn, $this->dbuser, $this->dbpass, $options);
} catch (PDOException $e) {
echo 'Unable to establish a database connection: ' . $e->getMessage();
exit();
}
}
}
and in my user class I passed it to the constructor
protected Database $conn;
public function __construct()
{
$this->conn = new Database;
}
but when i write a statement like this
$stmt = $this->conn->prepare($sql);
prepare is high lighted saying Method 'prepare' not found in \App\Database\Database
I would prefer not to use static or singleton

Your variable $conn would probably better be named $db:
protected Database $db;
public function __construct()
{
$this->db = new Database;
}
Then, when you understand that the User class has a property called db which in turn has a property called conn, the proper use might make more sense:
$stmt = $this->db->conn->prepare($sql);
However, you've defined $conn as protected, so you can't do that. You could make it public, or make a getter method in Database:
public function getConn(): PDO
{
return $this->conn;
}
And then do:
$stmt = $this->db->getConn()->prepare($sql);
Better, I'd forget all that and instead have Database extend PDO, then just override the constructor to configure with your custom values:
class Database extends PDO
{
public function __construct()
{
$dsn = sprintf(
'mysql:host=%s;dbname=%s;charset=%s',
DATABASE_HOST,
DATABASE_NAME,
DATABASE_CHARSET
);
parent::__construct(
$dsn,
DATABASE_USER,
DATABASE_PASS,
[
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]
);
}
}
Now you have a PDO-compatible database object that is pre-configured, and you can just use it like a regular PDO object:
class User
{
protected Database $db;
public function __construct(Database $db)
{
$this->db = $db;
}
public function whatever()
{
$this->db->prepare($sql);
// ...
}
}
$db = new Database();
$user = new User($db);
$user->whatever();

Related

How to create 2 PDO database connection instances PHP

I currently have a class which creates one database connection, however I would like to create another connection too. I have tried copying the class structure but just renaming the variables and functions however that doesn't work and it seems like it doesn't detect where my new PDO connection is as I get error Uncaught Error: Call to a member function prepare() on null in. What is the best approach to take in my case when creating 2 database connections?
config.php:
<?php
class Database
{
private $host = "localhost";
private $db_name = "database1";
private $username = "root";
private $password = "";
public $conn;
public function dbConnection()
{
$this->conn = null;
try
{
$this->conn = new PDO("mysql:host=" . $this->host . ";dbname=" . $this->db_name, $this->username, $this->password);
$this->conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
catch(PDOException $exception)
{
echo "Connection error: " . $exception->getMessage();
}
return $this->conn;
}
}
?>
class.user.php:
class USER
{
private $conn;
public function __construct()
{
$database = new Database();
$db = $database->dbConnection();
$this->conn = $db;
}
public function runQuery($sql)
{
$stmt = $this->conn->prepare($sql);
return $stmt;
}
}
You could create your User class and pass the connection to the constructor. This will give you flexibility to swap out the connection.
Regarding your database class, it seems to be a wrapper, to create a PDO connection. You could do away with it, or extend the class with different params.
Perhaps look at dependency injection, and containers. They might help you here.
<?php
class User
{
private $conn;
public function __construct(PDO $conn)
{
$this->conn = $conn;
}
public function runQuery($sql)
{
$stmt = $this->conn->prepare($sql);
return $stmt;
}
}

Call to undefined method PHP PDO

I am getting an error
Call to undefined method Connection::prepare()
The error originates on the select4 method of QueryBuilder class(below). The first line where the prepare method is called.
Ideally Connection class should return the PDO object to QueryBuilder.
It seems like i passed Connection object instance into the QueryBuilder.
Output from var_dump of private $pdo
object(QueryBuilder)#4 (1) {
["pdo":"QueryBuilder":private]=> object(Connection)#2 (0) { } }
object(Connection)#2 (0) { }
Explanation of code
I have a config class(not included) which accepts a json file (mysql connection info) and returns an array.
Its then passed into the Connection class to returns a new PDO object
class Connection {
public function __construct($db) {
$dsn = $db['dbs'] . ':host=' . $db['host'] . ';dbname=' . $db['dbName'];
return new PDO($dsn, $db['user'], $db['pwd']);
}
}
The QueryBuilder class accepts the PDO instance and stores it on the private variable.
class QueryBuilder {
private $pdo;
public function __construct($pdo) {
$this->pdo = $pdo;
}
public function select4($sql) {
$statement = $this->pdo->prepare(":sql :n");
$statement->bindValue(n, 4, PDO::PARAM_INT);
$statement->execute(['sql' => $sql]);
return $statement->fetch(PDO::FETCH_CLASS);
}
}
Call new instance of Connection passing array values from Config class
$pdo = new Connection(Config::getInstance()->get("db"));
Call new instance of QueryBuilder passing PDO instance from Connection
$query = new QueryBuilder($pdo);
$sql_1 = 'SELECT title, synopsis FROM blog LIMIT';
$blog = $query->select4($sql_1); // triggers error
Update:
Thanks for the comments and help. I changed the Connection class to implement a public static variable $pdo. I used the constructor to assign the PDO object to the static variable.
class Connection {
public static $pdo; // Added a static $pdo
public function __construct($db) {
$dsn = $db['dbs'] . ':host=' . $db['host'] . ';dbname=' . $db['dbName'];
self::$pdo = new PDO($dsn, $db['user'], $db['pwd']); // assign PDO
}
}
$pdo = new Connection(Config::getInstance()->get("db"));
$query = new QueryBuilder(Connection::$pdo);
Corrected a few minor mistakes in the select4 method and its working now. :)
Ideally Connection class should return the PDO object to QueryBuilder. It seems like i passed Connection object instance into the QueryBuilder.
You cannot achive that with constructor, returning a different object in constructor will not affect what is returned with new statement. See: Constructor returning value?.
You may try one of these approaches:
Inheritance
class Connection extends PDO {
public function __construct($db) {
$dsn = $db['dbs'] . ':host=' . $db['host'] . ';dbname=' . $db['dbName'];
parent::__construct($dsn, $db['user'], $db['pwd']);
}
}
use:
// Get Connection object which extends PDO class
$pdo = new Connection(Config::getInstance()->get("db"));
Static method
class Connection {
public static function getPDO($db) {
$dsn = $db['dbs'] . ':host=' . $db['host'] . ';dbname=' . $db['dbName'];
return new PDO($dsn, $db['user'], $db['pwd']);
}
}
use:
// Get PDO object using Connection static method
$pdo = Connection::getPDO(Config::getInstance()->get("db"));
Getter method
class Connection {
/** #var PDO */
private $pdo;
public function __construct($db) {
$dsn = $db['dbs'] . ':host=' . $db['host'] . ';dbname=' . $db['dbName'];
$this->pdo = new PDO($dsn, $db['user'], $db['pwd']);
}
/**
* Get PDO instance
* #return PDO
*/
public function getPDO() {
return $this->pdo;
}
}
use:
// Get PDO object using Connection object
$pdo = (new Connection(Config::getInstance()->get("db")))->getPDO();
You should definetely take a look at some mysql wrappers, like Laravel's database wrapper: https://github.com/illuminate/database. It is generally better to use checked solutions than reinvent the wheel.

PHP Out of class pdo connection fails

I can not make the PDO connection outside the class. Browser gives 500 error. I want to write PDO codes in out class. I am doing global change, but it is not.
try {
$pas = new PDO('mysql:host=localhost; dbname=tets', 'root', '123');
$pas->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
$pas->exec("SET CHARACTER SET utf8"); // return all sql requests as UTF-8
} catch (Exception $e) {
echo $e->getMessage();
}
/**
User Avatar Check
**/
class Usercheck
{
public $sql , $paso;
public function __construct()
{
global $pas;
$this->paso =& $pas;
}
public function smf_members($whatid)
{
$this->sql = $this->paso->query("SELECT * FROM smf_members WHERE id_member = $whatid");
}
public function fetchcheck(){
if ($this->sql) {
return $this->sql->fetch(PDO::FETCH_BOTH);
}
}
}
$memberuser = new Usercheck();
$memberuser->smf_members('1');
It is not a good practice to use $global. There are many solutions to achieve what you want to do, and here is my approach.
1) Create a Connection Class to handle the PDO connectivity.
use PDO;
/**
* PDO Connection Classes
* Used by Database wrapper class to ensure that there is only one connection
*/
class Connection
{
private $dsn = 'mysql:host=' . DB_HOST . ';dbname=' . DB_NAME. ';charset=' . DB_CHARSET;
private $conn;
private $error;
public function __construct()
{
try
{
$this->conn = new PDO($this->dsn, DB_USER, DB_PASSWORD);
}
catch(PDOException $e)
{
$this->error = $e->getMessage();
}
}
public function getConnection()
{
return $this->conn;
}
}
2) Establish the PDO connection via your wrapper class __construct(), the __construct() also ensure that there is only one PDO connection is instantiated.
use PDO;
class UserCheck
{
private $conn;
private $db;
public function __construct()
{
if (!$this->db) {
$conn = new Connection();
$this->db = $conn->getConnection();
}
}
}
I'm highly recommend that you read this PDO tutorial on how to use PDO and many best practice tips.

Call to a member function prepare() on null OOP

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.

Avoid creating new PDO in every class

function __construct($name, $lastName, $address, $rollNo)
{
$this->name = $name;
$this->lastName = $lastName;
$this->address = $address;
$this->rollNo = $rollNo;
$this->conn = new \PDO('mysql:host=localhost;dbname=students', 'root', '');
}
public function getUser($id){
$sql = "SELECT * FROM students WHERE id = ".intval($id);
foreach($this->conn->query($sql) as $row){
$user['name'] = $row['name'];
$user['address'] = $row['address'];
$user['roll_no'] = $row['roll_no'];
}
return $user;
}
So what i am trying to learn is how i can use a single PDO object in my all classes rather than creating $conn in all classes like users, courses etc.
I've came across the words Dependency Injection , Singleton , Factory and for me as a beginner these are all mixed up.
Also i want to know is it a bad practice what i am doing in my code for creating new objects for PDO.
A singleton is a control class that will ensure only one instance of a given object will exist at a time. So instead of calling $conn = new PDO() in every object, creating multiple instances of PDO(), you can write a singleton class named Database and use $conn = Database::instance() instead.
class Database
{
static $instance = null;
static function instance()
{
if (self::$instance == null)
self::$instance = new PDO('mysql:host=localhost;dbname=students', 'root', '');
return self::$instance;
}
}
Basically what it does is create a PDO() class if it doesn't exist, and reuse it if it was already instanced before.
As a side advantage, you also won't need to have your database credentials spread all over the project. The class Database will have it and noone else.
You could make a class called say 'Database' to deal with connecting to MySQL and setting up the PDO.
so something like...
class Database{
private $host = DB_HOST;
private $user = DB_USER;
private $pass = DB_PASS;
private $dbname = DB_NAME;
private $dbh;
private $error;
public function __construct(){
// setup dsn
$dns = 'mysql:host=' . $this->host . ';dbname=' . $this->dbname;
try{
$this->dbh = new PDO($dsn, $this->user, $this->pass);
}
catch(PDOException $e){
$this->error = $e->getMessage();
}
}
}
And then in your other classes just assign the class to a variable in your other classes so..
$db = new Database();
That would avoid your code duplication. You could also create functions in the Database class to carry out PDO queries as well.
Create a parent class with the connection in it and make a child class that extends the parent class
class Main {
protected $dbh;
function dbConnect() {
$options = array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION);
//DB_HOST, DB_NAME, DB_USER, DB_PASS are set via define()
//e.g. define("DB_HOST", "localhost");
$dsn = 'mysql:host=' . DB_HOST . ';dbname=' . DB_NAME;
$this->dbh = new PDO($dsn, DB_USER, DB_PASS, $options);
}
Then..
class User extends Main {
function crudRead() {
parent::dbConnect(); //called from the main class
$db = $this->dbh->prepare('SELECT * FROM user');
$db->execute();
return $db->fetchAll(PDO::FETCH_ASSOC);
}
}

Categories