Call to undefined method PHP PDO - php

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.

Related

Properly pass connection to connstructor PDO

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();

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.

static and non static method to work

I want to access a connexion database through PDO in a static way but I am still getting an error. This is my code :
require_once 'include/class.Settings.php';
class MonthlySums{
protected static $dbh;
public function __construct() {
$user = Settings::$db['user'];
$pass = Settings::$db['password'];
$dsn = 'mysql:host=' . Settings::$db['host'] . ';dbname=' . Settings::$db['db'];
try {
self::$dbh = new PDO($dsn, $user, $pass);
} catch(PDOException $e) {
die("Error! : " . $e->getMessage());
}
}
public static function get($init_id) {
$sql = "SELECT `year`, `month`, `gains_monthly_sum` FROM `fxs_gain_sums` WHERE `init_id` = '$init_id'";
$resultats = MonthlySums::$dbh->query($sql);
var_dump($resultats);
}
}
I have this error :
Fatal error: Call to a member function query() on a non-object in /home/public_html/gainlosssums.php on line 45
That line is : $resultats = MonthlySums::$dbh->query($sql);
How to make it work a static way ?
When you call a static method in a class, the constructor is not automatically invoked like when you create an instance of the class. With your code you would have to create at least one instance of the class so that the database connection would be set in your static variable. Then you would be able to call it in your desired static way.
If you still want to continue with your code, you could change it to this:
(Please note: this is the OP's modified code and I am not going into other aspects - just the result to the OP's question/problem)
require_once 'include/class.Settings.php';
class MonthlySums{
protected static $dbh;
public function __construct() {
$this->databaseLogin();
}
public static function databaseLogin(){
$user = Settings::$db['user'];
$pass = Settings::$db['password'];
$dsn = 'mysql:host=' . Settings::$db['host'] . ';dbname=' . Settings::$db['db'];
try {
self::$dbh = new PDO($dsn, $user, $pass);
} catch(PDOException $e) {
die("Error! : " . $e->getMessage());
}
}
public static function get($init_id) {
$sql = "SELECT `year`, `month`, `gains_monthly_sum` FROM `fxs_gain_sums` WHERE `init_id` = '$init_id'";
$resultats = self::$dbh->query($sql);
var_dump($resultats);
}
}
MonthlySums::databaseLogin();
As you can see there is a new static method databaseLogin and is called right after the class declaration. This way the static variable for the database connection is set and can be used in other methods.
You now can call it this way:
MonthlySums::get(1234);
Again, this might not be best practice but a solution to your problem.

PHP PDO There is no active transaction

I tried to run a query with PDO class and got this error message: "There is no active transaction" when trying to commit.
Here's my code:
public function runExQuery($sql) {
$preparedQuery = $this->connect()->prepare($sql);
$this->connect()->beginTransaction();
$preparedQuery->execute();
$this->connect()->commit();
}
private function connect() {
return new PDO('mysql:host=' . $this->host . ';dbname=' . $this->database . '', $this->username, $this->password);
}
What is the cause of this error? I explored previous posts of this type of question, but did not find any solutions.
Your ::connect() method is creating a new PDO each time you call it.
Since transactions do not survive outside of connections, the reconnecting wipes it out.
To correct this, store the PDO object as a class property:
class MyPdoClass
{
private $pdo;
// ...
public function connect()
{
if ($this->pdo instanceof PDO) {
return;
}
$this->pdo = new PDO(// ....
}
and then reference it after calling connect:
//...
public function runExQuery($query)
{
$this->connect();
$this->pdo->prepare($query);
// ...
}
You are creating a new PDO object every time you call $this->connect(), so if you have:
$stmt1 = $this->connect()->prepare(" ... ");
$stmt2 = $this->connect()->prepare(" ... ");
$stmt1 and $stmt2 will actually be completely different PDO objects, so if you start a transaction with one object, it will NOT apply to the other one. Instead you should save a PDO object and reference that instead of creating a new one every time.
Most of the time, I find it easier just to pass this to the class's constructor, however if you want to do minimal editing, you could just do:
class YourClass {
private $dbh;
private function connect() {
if (!isset($this->dbh)) {
$this->dbh = new PDO('mysql:host=' . $this->host . ';dbname=' . $this->database, $this->username, $this->password);
}
return $this->dbh;
}
}
However you might want to change the name connect() to something a bit more logical, like getDbh().
If instead you wanted to put it in the object's constructor, you could do this:
class YourClass {
private $dbh;
public function __construct(PDO $dbh) {
$this->dbh = $dbh;
}
}
$dbh = new PDO('mysql:host=' . $host . ';dbname=' . $database, $username, $password);
$yourclass = new YourClass($dbh);
Then in any of the other class methods, you would just reference $this->dbh. Using your code as an example:
public function runExQuery($sql) {
$preparedQuery = $this->dbh->prepare($sql);
$this->dbh->beginTransaction();
$preparedQuery->execute();
$this->dbh->commit();
}
Personally, this is the way I would do it.

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