The Situation
I'm fairly new to object-oriented programming in PHP and currently I'm creating a small CMS for learning purposes. I've learned a lot about OOP on my way, but I'm facing a weird issue at the moment. I've created a Singleton class to deal with the database connection and queries.
public static function getInstance()
{
if(!isset(self::$instance))
{
self::$instance = new Database();
}
return self::$instance;
}
In the same class, there also is a method to execute queries. It takes two parameters, the query and an optional array with parameters to bind for the prepared statements. You can see its source below.
public function execute($query, $params = array())
{
$this->error = false; // Set 'error' to false at the start of each query.
if($this->query = $this->pdo->prepare($query))
{
if(!empty($params))
{
$index = 1;
foreach($params as $parameter)
{
$this->query->bindValue($index, $parameter);
++$index;
}
}
if($this->query->execute())
{
$this->results = $this->query->fetchAll(PDO::FETCH_OBJ);
$this->count = $this->query->rowCount();
}
else
{
$this->error = true;
}
}
return $this;
}
The Problem
If I have multiple queries on the same page, the results and count variables still contain the values of the first query. Imagine the following - the first query retrieves all users from my database. Let's say there are 15. The second query retrieves all blog posts from the database, let's say there are none. If no posts are present, I want to display a message, otherwise I run a loop to display all results. In this case, the loop is executed even though there are no blog posts, because the count variable is used to determine if there are posts in the database and it still holds the 15 from the first query somehow.
This obviously leads to some errors. Same with results. It still holds the value from the first query.
$query = Database::getInstance()->execute('SELECT * FROM articles ORDER BY article_id DESC');
if(!$query->countRes())
{
echo '<h2>There are no blog posts in the database.</h2>';
}
else
{
foreach($query->results() as $query)
{
echo '<article>
<h3>'.$query->article_heading.'</h3>
<p>'.$query->article_preview.'</p>
</article>';
}
}
The countRes() and results() methods simply return the variables from the DB class.
I hope that I have explained the problem understandable. Responses are very appreciated.
I would use a response object to avoid attaching query specific data to the global database object.
Example:
<?php
class PDO_Response {
protected $count;
protected $results;
protected $query;
protected $error;
protected $success = true;
public function __construct($query){
$this->query = $query;
try{
$this->query->execute();
}catch(PDOException $e){
$this->error = $e;
$this->success = false;
}
return $this;
}
public function getCount(){
if( is_null( $this->count ) ){
$this->count = $this->query->rowCount();
}
return $this->count;
}
public function getResults(){
if( is_null( $this->results ) ){
$this->results = $this->query->fetchAll(PDO::FETCH_OBJ);
}
return $this->results;
}
public function success(){
return $this->success;
}
public function getError(){
return $this->error;
}
}
Then in your database class:
public function execute($query, $params = array())
{
if($this -> _query = $this -> _pdo -> prepare($query))
{
if(!empty($params))
{
$index = 1;
foreach($params as $parameter)
{
$this -> _query -> bindValue($index, $parameter);
++$index;
}
}
return new PDO_Response($this->_query);
}
throw new Exception('Some error text here');
}
UPDATE: Moved execution into response class for error handling
Example usage (not tested)
$select = $database->execute('SELECT * FROM table');
if( $select->success() ){
//query succeeded - do what you want with the response
//SELECT
$results = $select->getResults();
}
$update = $database->execute('UPDATE table SET col = "value"');
if( $update->success() ){
//cool, the update worked
}
This will help fix your issue in the event that subsequent queries fail, there will not be any old query data attached to the database object.
Related
$db->select("users")->where(array("username", "=", "username"));
$db->update("users", array("username" => "username", "password" => "12345"))->where(array("id", "=", "14"));
Ok, I want to write the statements like above, by chain the where() method onto select, update and delete.
My problem is; how to determine if I used the select, update or delete before the where, so I can bind the right values onto the right statement.
I want something like this:
public function where() {
if($this->select()) {
// so if $db->select("users")->where(array("username", "=", "username"));
// save the where data in the select variable.
}
elseif($this->update()) {
// so if $db->update("users", array("username" => "username", "password" => "12345"))->where(array("id", "=", "14"));
// save the where data in the update variable.
}
elseif($this->delete()) {
// so if $db->delete("users")->where(array("username", "=", "username"));
// save the where data in the delete variable.
}
}
But the code above is of course not valid, and I dont use any frameworks.
public function select($table, $what = null) {
$what == null ? $what = "*" : $what;
$this->_select = "SELECT {$what} FROM {$table}";
return $this;
}
You would have to maintain that state. It's not about telling whether the previous call was a select() or an update(), that's the wrong way to think about the problem. You just need each of select/update/delete to modify $this, so that $this, always knows what kind of query it's building.
A dead simple example:
public function select() {
$this->kind == 'select';
return $this;
}
public function where() {
if ($this->kind == 'select') {
...
return $this;
}
The only thing that your chained methods share is that they each return $this, so that a subsequent method can be chained onto the end. It's all about storing up state in $this until some final method call actually evalates the built-up query.
Something like:
public function select($table, $fields = '*')
{
$this->query = "SELECT {$fields} FROM `{$table}`";
return $this;
}
public function where($conditions = [])
{
if ($this->query)
{
if ($conditions)
{
foreach ($conditions as $key => &$val)
$val = "`{$key}` = '{$val}'";
$this->query .= ' WHERE ' . implode(' AND ', $conditions);
}
$db->query($this->query);
$this->query = '';
return $this;
}
}
This would work, however, you have to notice that this structure would allow you to do things like:
$db->where();
This is perfectly valid even though doesn't make sence to call where() in the database directly.
Also, queries that don't require a WHERE clause would not run, because only where() actually makes the call.
How to solve this?
We can actually use a very interesting mechanic of OOP: The destructor method. PHP destroys objects immediately after they are no longer in use, and we can explore this feature here as a trigger to run the query. We only have to separate the query to a new object.
class dbQuery
{
private $db;
private $conditions = [];
function where($conditions = [])
{
$this->conditions = array_merge($this->conditions, $conditions);
return $this;
}
function __construct($db, $query)
{
$this->db = $db;
$this->query = $query;
}
function __destruct()
{
if ($this->conditions)
{
foreach ($this->conditions as $key => &$val)
$val = "`{$key}` = '{$val}'";
$this->query .= ' WHERE ' . implode(' AND ', $this->conditions);
}
$this->db->result = $db->query($this->query);
}
}
class Database
{
public $result = null;
protected static $instance;
function __construct()
{
if (!self::$instance)
self::$instance = new mysqli('localhost', 'user', 'password', 'dbname');
}
public function query($query)
{
return self::$instance->query($query);
}
public function select($table, $fields = '*')
{
return new dbQuery($this, "SELECT {$fields} FROM `{$table}`");
}
}
This way $db->where() won't work as it doesnt exist, and using $db->select('table') or $db->select('table')->where([...]) will both give results, and even allows extending the syntax to use where() multiple times like:
$db->select('table')->where(['id' => 100])->where(['price' => 1.99]);
I have a DB class that I've created several functions in to return various values. One of the functions returns (or is supposed to) a "user" class object that represents a logged in user for the application.
class user {
public $guid = '';
public $fname = '';
public $lname = '';
public function __construct() {}
public function print_option() {
return "<option value='$this->guid'>$this->name</option>";
}
}
In the DB class I have the following 2 functions:
public function get_user($uid) {
$sql = '';
$usr = new user();
$sql = "SELECT guid, fname, lname FROM ms.users WHERE guid=?";
if($sth = $this->conn->prepare($sql)) {
$sth->bind_param('s', $uid);
if($sth->execute()) {
$sth->bind_result($usr->guid, $usr->fname, $usr->lname);
$sth->fetch();
print_r($usr); // PRINTS OUT CORRECTLY
return $usr;
}
else {return null;}
}
else {return null;}
}
public function get_practice_user_list($pguid) {
$ret = '';
$sql = "SELECT user_guid FROM ms.perm WHERE p_guid=?";
if($sth = $this->conn->prepare($sql)) {
$sth->bind_param('s', $pguid);
if($sth->execute()) {
$usr = new user();
$guid = '';
$sth->bind_result($guid);
while($sth->fetch()) {
print_r($guid); // PRINTS GUID correctly
$usr = $this->get_user($guid);
print_r($usr); // PRINTS NOTHING object is null so prints "error" two lines later.
if($usr != null) $ret .= $usr->print_option();
else print "error";
}
return $ret;
}
else {return null;}
}
else {return null;}
}
I'm just not understanding why the "user" object is not returning in this instance. Others calls to the get_user function work just fine and return the user class object pertaining to that user.
TIA
I guess you guid may be an integer so
$sth->bind_param('s', $uid);
bind_param's first param should be 'i' not 's';
http://www.php.net/manual/en/mysqli-stmt.bind-param.php
The problem was with the query. Since the code was just looping through one query (get_practice_user_list), then calling the get_user function and attempting a second query MySQL came back with an error of out of sync message. When I looked that up, I was able to fix it by doing a fetch_all on the first query then looping through that array to get the users.
I am trying to make a 1 on 1 chat website by learning as I progress, but I've come to a hault.
I can't write to the database.
I have four php files linked below.
Index
Init:
session_start();
define('LOGGED_IN', true);
require 'classes/Core.php';
require 'classes/Chat.php';
?>
Chat
Core:
class Core {
protected $db, $result;
private $rows;
public function __construct() {
$this->db = new mysqli("localhost","root","");
}
public function query($sql) {
$this->result = $this->db->query($sql);
}
public function rows() {
for($x = 1; $x <= $this->db->affected_rows; $x++) {
$this->rows[] = $this->result->fetch_assoc();
}
return $this->rows;
}
}
?>
I have a MySql database set with WAMP.
P.S. Yes, I have opened the "< ? php"
but it doesn't get displayed here.
From what I have seen you do not select a default database. You must either give a default database in
$this->db = new mysqli("localhost","root","", "mydatabase");
or select one later with
$this->db->select_db("mydatabase");
You also don't check the return values of the mysql calls. For example, add
public function query($sql) {
$this->result = $this->db->query($sql);
if ($this->result === false) {
echo $this->db->error;
}
}
after your mysql statements, in order to see whether the statements succeed or fail.
For debugging purposes you can display the sql and corresponding result
public function query($sql) {
var_dump($sql);
$this->result = $this->db->query($sql);
var_dump($this->result);
echo $this->db->error;
}
I am running an insert query using PDO and then getting the newly created Id with lastInsertId(). This is all working on my localhost environment.
When I move the exact same code onto a server, the lastInsertId() is always returning blank, even though the insert statement works and inserts the new row into the database. Would this be a setting in my configuration? Any help is appreciated.
$insertstmt = $dbinsert->prepare($insertsql);
// bindParams ...
$insertstmt->execute();
// always blank
$id = $dbinsert->lastInsertId();
$dbinsert = null;
I ended up replacing lastInsertId() by using this method: http://php.net/manual/en/pdo.lastinsertid.php#105580
$temp = $sth->fetch(PDO::FETCH_ASSOC);
I've describe a solution for this problem on my blog. I couldn't directly modify sql queries in the code, so I extended the PDO class in this way
class BetterPDO extends \PDO
{
protected $stmt;
protected $withIdentitySelect;
public function prepare($statement, $driver_options = null)
{
$this->$withIdentitySelect = false;
if (preg_match('/^insert/i', $statement)) {
$statement = "{$statement}; select SCOPE_IDENTITY() AS 'Identity';";
$this->$withIdentitySelect = true;
}
$this->stmt = parent::prepare($statement, is_array($driver_options) ? $driver_options : []);
return $this->stmt;
}
public function query($statement)
{
$this->$withIdentitySelect = false;
if (preg_match('/^insert/i', $statement)) {
$statement = "{$statement}; select SCOPE_IDENTITY() AS 'Identity';";
$this->$withIdentitySelect = true;
}
$this->stmt = parent::query($statement);
return $this->stmt;
}
public function lastInsertId($name = null)
{
$lastIndex = 0;
if (($this->$withIdentitySelect) && ($this->stmt->columnCount() > 0)) {
$lastIndex = $this->stmt->fetchColumn(0);
$this->stmt->closeCursor();
}
return $lastIndex;
}
}
I'm a little confused at the moment because my DB class doesn't seem to be storing any results after a query (i.e. the 'results' property is still an empty array). The unsual thing is that when I transfer my logic outside a class definition it works perfectly.
My code with database credentials blanked out:
namespace DatabaseConnection;
use PDO;
class DB {
/*****STATES*****/
private $con;
private $results;
/*****METHODS*****/
public function init(){
$this->con = new PDO("***************************");
$this->results = array();
return $this;
}
public function getInfo(){
if($this->con === null ) return "No Connection";
else return "Connected";
}
public function getResults(){
return $this->results;
}
public function retrieve(){
$query = $this->con->prepare("select * from documents");
$query->execute();
while($row = $query->fetch(PDO::FETCH_ASSOC)){
$this->results[] = $row;
}
return $this;
}
Try:
public function retrieve(){
$query = $this->con->prepare("select * from documents");
$query->execute(); //<---------
while($row = $query->fetch(PDO::FETCH_ASSOC)){
$this->results[] = $row;
}
return $this;
}
OP is preparing a statement and then never executes it, and has no way of detecting this because of the utter lack of any error handling on the DB calls. – Marc B