Erdnussknacker Erdnussknacker - 7 months ago 10
SQL Question

PHP variables not getting overwritten on subsequent queries

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.

Answer

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.