views:

171

answers:

3

PHP/MySQLisolating database access in class - how to handle multiple row Selects

Here’s a coding question. I isolated all DB access functions in a class

<?php
class DB {
    var $conn;         
    function DBClass () {
                @$this-> conn = mysqli_connect (DB_SERVER, DB_USER, DB_PASS, DB_NAME);
    }
    function validateUser ($aUserid, $aPassword) {
                …  validation code – sql injection code etc..
                $sql = "Select userid, name, level From users where userid = '$aUserid' and password = '$aPassword'";
                $result = mysqli_query ( $this->conn, $sql );
                if (!$result || (mysqli_num_rows ($result) < 1)) {
                            return false;
                }
                $dbarray = mysqli_fetch_assoc ($result); // get a row
                return $dbarray;
    }

    function getProduct ($aProductid) {

                return $dbarray;
    }

    function getProductList () {  
                // <----------- this would be the problem function
    }
}

$DB = new DBClass();
?>

My calling routine:

<?php
        $dbarray = $DB->validateUser ($_POST['userid'], $_POST['password']); 
?>

No problem it works fine. I run into a problem with a result set of more than one row. Now I have to get back to the class object for each row. It’s no problem if I include the MySQL code in the calling routine, but I’d like to keep it isolated in my class and I’m not sure how to code it.

Any thoughts? Any examples?

+1  A: 

you could save the result in an array and return it:

function getProductList () {
   $sql = "SELECT ...";
   $result = mysqli_query ( $this->conn, $sql );

   $myProducts = array();

   while ($row = mysqli_fetch_assoc($result))
      $myProducts[] = $row; // or array_push($myProducts, $row)
   }

   return $myProducts
}

As a result you'll have an array of arrays and each element of it will contain one row of the result.

stefita
Thanks, that will work.What would you suggest for a very large result set, that is too large for an array?
sdfor
you could store the result in a new class member var and write a function which gets the next row. Perhaps create a new class which deals with results?
stefita
frameworks like cake do this...
Mark
A: 

You have a SQL injection right in your login page.

What happens if someone inputs that as password: xxx' OR 'yyy' <> 'x

Ramon Poca
I didn't show the code that dealt with validation, because it wasn't germane to my question.I validate the fields for only letters and numbers. I don't allow special characters. I allow one ' for O'reilly in the name. If I were to allow special characters then I would have to be more careful and code it differently. One way is use sql Prepare. function validateUser ($aUserid, $aPassword) { HERE->> … validation code – sql injection code etc.. $sql = "Select userid, name, level From users where userid = '$aUserid' and password = '$aPassword'";
sdfor
+1  A: 

If you use PHP 5.3.0 and mysqlnd, you can use the new function mysqli_fetch_all(). This returns an array of associative arrays.

If you use an earlier version of PHP, you could switch to using PDO, and use the function PDOStatement::fetchAll().

You ask in a comment what about a very large result set. It's true that an unbounded result set could cause the array to exceed your PHP memory limit and that would cause a fatal error and halt the script. But is this really a problem? How many products do you have? You could use LIMIT to make sure the query isn't unbounded.


Re the other part of your questions regarding going back to a class, I'd suggest making an Iterator class:

class DB implements IteratorAggregate
{
  protected $_data = array();

  public function getProductList() {
    // fetch all results from SQL query, stuff them into $this->_data
    return $this->getIterator();
  }

  public function getIterator() {
    return new ArrayIterator($this->_data);
  }
}

Now you can use the class in a foreach loop:

$db = new DB();
foreach ($db->getProductList() as $product) {
  // do something with each product
}

The IteratorAggregate interface means you can even do this:

$db = new DB();
$db->getProductList();
// ...other steps...
foreach ($db as $product) {
  // do something with each product
}

Of course you could only store one result set at a time with this method. If you used your DB class for any other queries in the meantime, it would complicate things. For this reason, most people don't try to write a single class to encapsulate all database operations. They write individual classes for each type of Domain Model they need to work with, decoupled from the database connection.

Bill Karwin
Thanks Bill, that's very informative. I tried mysqli_fetch_all() and got an unknown function error. I installed with XAMPP recently so I believe I have the latest versions. Is there something I need to do to activate mysqlnd ?thanks again
sdfor
According to the docs, that function depends on PHP 5.3.0 and the mysqlnd driver. You should be able to find out what versions and extensions you have using `phpinfo()`.
Bill Karwin
ah, you are right. I just checked XAMPP 1.7.1 uses php 5.2.9XAMPP 1.7.2 recently released (Aug 10) uses PHP 5.3.0, but there's no update package. So I'll either wait or uninstall and install 1.7.1.Thanks again.My original question really had a subtext to it related to oop, and that is, if you need to go back to a class to get additional data from a class function previously called, how would you do it?
sdfor
I'll add that as an edit above.
Bill Karwin
once again, thanks Bill. The iterator class is good. I had no idea it existed. It's nice to find someone who not only has answers, but understand the questions.
sdfor
Yeah, the IteratorAggregate is part of a PHP extension called SPL. The developers of SPL completely do not get it that they need to write documentation. I talked to the SPL project leader and I offered to help write docs, and he said, "sure, we need someone to write build scripts for generating documentation automatically from the code."
Bill Karwin